Compare commits

...
54 Commits
Author SHA1 Message Date
smanylov 587d7ceead remove console.log 2026-06-02 12:50:15 +07:00
smanylov ac8ef8caa2 add barear for headers 2026-06-02 11:45:02 +07:00
smanylov 7d0fb265d4 add bearer tokens for functions 2026-06-02 11:39:54 +07:00
smanylov 743db57bb3 refactor tokens 2026-06-01 14:48:13 +07:00
smanylov d7d2e36573 increment version 2026-06-01 12:27:58 +07:00
smanylov 8b157b8e87 fix useViolationStatistic 2026-05-29 22:27:08 +07:00
smanylov 0935e8cbc7 fix send appeals 2026-05-29 13:05:49 +07:00
smanylov f04ce348ae add appeal modal window, add file status 2026-05-28 20:30:16 +07:00
smanylov 508e0d182c fix crush 2026-05-25 16:59:04 +07:00
smanylov dbd5d43d65 add translate for closed status 2026-05-25 15:01:42 +07:00
smanylov 7d8a725e34 cange location layout for history view table 2026-05-25 14:53:09 +07:00
smanylov 491adb4090 fix pagination info for all tanstak-tables 2026-05-25 14:22:41 +07:00
smanylov 1a3f5277d9 fix translate 2026-05-25 13:42:11 +07:00
smanylov ffcd5fc535 fix complaints/violations/matches statistick 2026-05-25 13:40:49 +07:00
smanylov 65e0c2ed62 fix expired tariff date 2026-05-22 11:58:42 +07:00
smanylov 36b4a3b481 add start-end-date range for match list and fix pagination 2026-05-21 18:20:50 +07:00
smanylov e0455b200a conect endpoints for FilePpageFileStatistic 2026-05-20 11:59:52 +07:00
smanylov 348a5398d0 change FinalSummary numbers 2026-05-20 11:47:34 +07:00
smanylov af87bfc7b6 fix crush 2026-05-20 11:21:24 +07:00
smanylov beefe86ffd change notification badge size 2026-05-20 11:17:36 +07:00
smanylov 3a7c609588 remove pagination-info-files 2026-05-20 11:09:15 +07:00
smanylov aeeee04166 fix pagination 2026-05-18 12:05:12 +07:00
smanylov 288d6782a4 remove console.log 2026-05-18 11:55:16 +07:00
smanylov 55915af9df rename function 2026-05-18 11:51:56 +07:00
smanylov bfc457470c increment version 2026-05-16 16:56:59 +07:00
smanylov 210145f5ca fix sorting for document view 2026-05-16 16:56:31 +07:00
smanylov d0793a281e change dropdown on link for dashboard user files 2026-05-14 15:22:35 +07:00
smanylov 6994edd216 add locale for document history view 2026-05-14 14:45:49 +07:00
smanylov 3a0ae93846 increment version 2026-05-14 14:30:53 +07:00
smanylov 13a5db6313 add notification context 2026-05-14 14:30:20 +07:00
smanylov 9c011b6416 rename opened document history 2026-05-13 12:15:11 +07:00
smanylov 294e16e1a6 hide select button for notification when notification list is null 2026-05-13 10:57:20 +07:00
smanylov 71f7120239 rename session 2026-05-12 11:50:35 +07:00
smanylov 0e28986dc7 rename functions 2026-05-11 14:10:42 +07:00
smanylov 51a3ed7d18 fix translate 2026-05-11 13:49:29 +07:00
smanylov c15867a41c fix complaint view bug 2026-05-11 13:37:06 +07:00
smanylov c4e4d775f5 fix some crashes 2026-05-08 15:48:26 +07:00
smanylov cd07502128 add translate 2026-05-08 15:37:25 +07:00
smanylov 1ef2cc227a remov olden that month filter for violation table 2026-05-08 15:31:41 +07:00
smanylov 7226285746 change swiper pagination block 2026-05-08 14:30:06 +07:00
smanylov dd93cd302c add space for translate 2026-05-08 14:13:02 +07:00
smanylov 37b7ccb52e fix loading 2026-05-08 13:57:55 +07:00
smanylov b8a76bd6a9 add file download for tracking page 2026-05-08 13:39:37 +07:00
smanylov 07e1237f05 add tracking statistic 2026-05-07 12:06:02 +07:00
smanylov 29546740c7 add styles for filtres 2026-05-06 19:17:47 +07:00
smanylov 42cc96a28a remove log 2026-05-06 19:13:48 +07:00
smanylov 84b07af246 fix pagination 2026-05-06 19:12:37 +07:00
smanylov 8a72dbaf68 add TrackingHistoryView 2026-05-06 19:03:29 +07:00
smanylov af6b997ceb increment version 2026-05-05 16:07:54 +07:00
smanylov e97dd392c0 fix download and download name for tracking document 2026-05-05 16:01:08 +07:00
smanylov ae0526a58a change notification count number rules 2026-05-05 15:30:26 +07:00
smanylov d419c1c2cb add permission change button 2026-05-05 13:07:52 +07:00
smanylov 48e03b4237 increment version 2026-05-04 20:31:31 +07:00
smanylov 2fad519b7e add file info fro document view 2026-05-04 20:31:15 +07:00
61 changed files with 2124 additions and 741 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "no-copy-frontend",
"version": "0.105.0",
"version": "0.120.0",
"private": true,
"scripts": {
"dev": "next dev -p 2999",
+4 -3
View File
@@ -1,9 +1,10 @@
import Providers from '@/app/providers/getQueryServer'
import type { Metadata } from "next";
import { NextIntlClientProvider, hasLocale } from 'next-intl';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
import { ToastProvider } from '@/app/providers/ToastProvider';
import Providers from '@/app/providers/getQueryServer';
import { Suspense } from 'react';
import "../styles/globals.css";
import "../styles/global-styles.scss";
@@ -30,9 +31,9 @@ export default async function RootLayout({
}
return (
<html lang={locale}>
<html>
<body>
<NextIntlClientProvider>
<NextIntlClientProvider locale={locale}>
<Providers>
{children}
<ToastProvider />
+5 -3
View File
@@ -48,7 +48,7 @@ export interface FileDetails {
interface PageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ page?: string, status?: string , violationId?: string, caseType?: 'complaint' | 'claim'}>;
searchParams: Promise<{ page?: string, status?: string, violationId?: string, caseType?: 'complaint' | 'claim', startDate: string, endDate: string }>;
}
export default async function Page({
@@ -56,7 +56,7 @@ export default async function Page({
searchParams
}: PageProps) {
const { id } = await params;
const { page, status, violationId, caseType } = await searchParams;
const { page, status, violationId, caseType, startDate, endDate } = await searchParams;
const currentPage = Number(page) || 1;
try {
@@ -71,7 +71,7 @@ export default async function Page({
>
<FilePageFileInfo fileInfo={fileInfo} />
{/* <FilePageActions /> */}
<ViolationPpageFileStatistic />
<ViolationPpageFileStatistic id={id} />
</div>
<div>
<FilePageViolationsList
@@ -80,6 +80,8 @@ export default async function Page({
fileId={id}
violationId={violationId}
caseType={caseType}
startDate={startDate}
endDate={endDate}
/>
{/* <FilePageNote /> */}
{/* <FilePageViolationInfo /> */}
+2
View File
@@ -2,6 +2,7 @@
import { TrackingStatistic } from '@/app/ui/tracking-page/tracking-statistic';
import FilesTable from '@/app/ui/dashboard/files-table';
import { TrackingHistoryView } from '@/app/ui/tracking-page/tracking-history-view';
export default async function Page() {
const FILE_TYPE = "document";
@@ -12,6 +13,7 @@ export default async function Page() {
>
<TrackingStatistic />
<FilesTable fileType={FILE_TYPE} showFileLink={true} />
<TrackingHistoryView />
</div>
)
}
+5
View File
@@ -1,9 +1,14 @@
'use client'
import { PdfProvider } from '@/app/contexts/PdfContext';
import { WatchDocProviders } from '@/app/providers/WatchDocProviders';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<WatchDocProviders>
<PdfProvider>
{children}
</PdfProvider>
</WatchDocProviders>
);
}
+85 -25
View File
@@ -4,65 +4,124 @@ import Link from 'next/link';
import PdfViewer from '@/app/components/PdfViewer';
import { DownloadButton } from '@/app/components/document-viewer/DownloadButton';
import { CopyLinkButton } from '@/app/components/document-viewer/CopyLinkButton';
import { fetchDocumentInfo } from '@/app/actions/trackingActions';
import { LoginUserBage } from '@/app/components/document-viewer/LoginUserBage';
interface PageProps {
searchParams: Promise<{ docid: string; title?: string }>;
}
interface DocumentInfo {
checksCount?: number;
checksum?: string;
createdAt?: string;
downloadUrl?: string;
existsOnDisk?: boolean;
fileExtension?: string;
fileFormat?: string | null;
fileName?: string;
filePath?: string;
fileSize?: number;
fileUploadDate?: string;
formattedSize?: string;
id: string;
mimeType?: string;
monitoring?: string;
originalFileName?: string;
ownerCompany?: string | null;
ownerEmail?: string;
ownerName?: string;
permissions?: {
DOWNLOAD?: boolean;
};
protectStatus?: string;
protectedFilePath?: string;
status?: string;
storedFileName?: string;
supportId?: number;
thumbnailFileUrl?: string;
updatedAt?: string;
uploadSessionId?: string;
userId?: number;
}
export default async function Page({ searchParams }: PageProps) {
const { docid, title } = await searchParams;
const documentTitle = title || 'FileName';
const ownerName = "Name Name Name";
const companyName = "companyName";
let response: DocumentInfo | null = null;
let error: string | null = null;
try {
response = await fetchDocumentInfo(docid);
} catch (err) {
error = 'Не удалось загрузить информацию о документе';
}
const documentTitle = response?.fileName || 'FileName';
if (error || !response) {
return (
<div className="watch-doc-wrapper">
<div className="watch-doc-page">
<header className="watch-doc-header">
<Link href="#" className="header-logo">NoCopy</Link>
<LoginUserBage />
</header>
<div className="error-container">
<h2>Ошибка загрузки документа</h2>
<p>{error || 'Документ не найден'}</p>
</div>
</div>
</div>
);
}
return (
<div className="watch-doc-wrapper">
<div className="watch-doc-page">
<header className="watch-doc-header">
<Link href="#" className="header-logo">NoCopy</Link>
<Link href="#" className="header-user">Name</Link>
<LoginUserBage />
</header>
<div className="watch-doc-status">
<div className="st-title">Защита подтверждена</div>
<div className="st-sub">
Документ <strong>{documentTitle}</strong> зарегистрирован и защищён платформой NoCopy.
Документ <strong>{response?.fileName ? response?.fileName : '#'}</strong> зарегистрирован и защищён платформой NoCopy.
</div>
</div>
<div className="watch-doc-grid">
<div>
<div
className="flex flex-col justify-between"
>
<div className="watch-doc-card">
<div className="card-head">Правообладатель</div>
<div className="card-body">
<div className="owner-row">
<div className="owner-av">Н</div>
<div className="owner-av">
{response?.fileName ? response?.fileName[0] : ''}
</div>
<div>
<div className="owner-name">{ownerName}</div>
{/* <div className="owner-meta">{companyName}</div> */}
{response?.ownerName && (
<div className="owner-name">{response?.ownerName}</div>
)}
{response?.ownerCompany && (
<div className="owner-name">{response?.ownerCompany}</div>
)}
{response?.ownerName && (
<div className="owner-meta">Зарегистрирован в NoCopy</div>
)}
</div>
</div>
</div>
</div>
{/* <div className="watch-doc-card">
<div className="card-head">Скачать документ</div>
<div className="card-body">
<DownloadButton />
<div className="download-description">
Вы вошли как <strong>{ownerName}</strong>.
<br />
Скачивание будет зарегистрировано.
</div>
</div>
</div> */}
<div className="watch-doc-actions">
<DownloadButton />
{response?.permissions?.DOWNLOAD && (
<DownloadButton fileId={docid} fileName={response?.fileName}/>
)}
<CopyLinkButton />
<Link href="#" className="btn btn-out" target="_blank">О NoCopy</Link>
</div>
</div>
@@ -73,6 +132,7 @@ export default async function Page({ searchParams }: PageProps) {
<PdfViewer
fileId={docid}
fileName={documentTitle}
showDownload={response.permissions?.DOWNLOAD}
/>
</div>
</div>
@@ -81,8 +141,8 @@ export default async function Page({ searchParams }: PageProps) {
<div className="watch-doc-footer">
<Link href="#">NoCopy</Link>
<Link href="#">Условия</Link>
<Link href="#">Конфиденциальность</Link>
<Link href="/terms-of-use">Условия</Link>
<Link href="/privacy-policy">Конфиденциальность</Link>
</div>
</div>
</div>
+10 -6
View File
@@ -44,12 +44,12 @@ export async function getUserFilesInfo() {
msg_id: 20005,
message_body: {
action: 'user_files_info',
token: token
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -81,13 +81,13 @@ export async function fetchTariffs(tariffTerm: 'MONTHLY' | 'YEAR') {
msg_id: 30001,
message_body: {
action: 'get',
user_token: token,
tariff_term: tariffTerm
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -108,6 +108,7 @@ export async function fetchTariffs(tariffTerm: 'MONTHLY' | 'YEAR') {
}
export async function fetchTokensBundle() {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
@@ -122,7 +123,8 @@ export async function fetchTokensBundle() {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -143,6 +145,7 @@ export async function fetchTokensBundle() {
}
export async function getBuildData() {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/check/api/build`, {
@@ -150,6 +153,7 @@ export async function getBuildData() {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -165,7 +169,7 @@ export async function getBuildData() {
export async function fetchINN(inn: string) {
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
const response = await fetch(`${API_BASE_URL}/api/auth`, {
method: 'POST',
body: JSON.stringify({
version: 1,
+9 -12
View File
@@ -72,7 +72,7 @@ export async function authorization(
try {
const { email, password } = validatedFields.data;
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
const response = await fetch(`${API_BASE_URL}/api/auth`, {
method: 'POST',
body: JSON.stringify({
version: 1,
@@ -89,6 +89,7 @@ export async function authorization(
});
if (response.ok) {
let parsed = await response.json();
if (parsed.message_code === 0) {
await createSession(parsed.message_body.token, email);
} else {
@@ -234,7 +235,7 @@ export async function registration(
try {
const { fullName, email, password, phone } = validatedFields.data;
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
const response = await fetch(`${API_BASE_URL}/api/auth`, {
method: 'POST',
body: JSON.stringify({
version: 1,
@@ -400,13 +401,11 @@ export async function tokenLifeExtension() {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
version: 1,
msg_id: 20008,
message_body: {
token: token
}
msg_id: 20008
}),
});
@@ -431,14 +430,14 @@ export async function confirmEmail(userId: number, verifyToken: string) {
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
'Authorization': `Bearer ${verifyToken}`,
},
body: JSON.stringify({
version: 1,
msg_id: 20009,
message_body: {
resend: 0,
user_id: userId,
verify_token: verifyToken
user_id: userId
}
}),
});
@@ -474,7 +473,6 @@ export async function confirmEmail(userId: number, verifyToken: string) {
export async function resendConfirmEmail(userId: number) {
console.log('resendConfirmEmail');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
@@ -524,10 +522,9 @@ export async function resetPassword(
formData: FormData,
) {
const email = formData.get('email');
console.log('resetPassword');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
const response = await fetch(`${API_BASE_URL}/api/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -611,6 +608,7 @@ export async function confirmPassword(
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${verifyToken}`
},
body: JSON.stringify({
version: 1,
@@ -618,7 +616,6 @@ export async function confirmPassword(
message_body: {
email: email,
password: password,
verifyToken: verifyToken,
action: "confirmVerification"
}
}),
+2 -10
View File
@@ -95,7 +95,6 @@ export async function companyUserRegistration(
phone: phone,
password,
accountType: 'b2b',
authToken: token,
mail_verified: false,
ip: ip,
user_agent: userAgent,
@@ -103,7 +102,8 @@ export async function companyUserRegistration(
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -113,14 +113,6 @@ export async function companyUserRegistration(
message_body: { token: string },
message_code: number
};
console.log({
fullName: fullName,
email,
phone: phone,
password,
accountType: 'b2b',
authToken: token
});
if (parsed.message_code === 0) {
console.log(`registration - ${email}`);
+47 -7
View File
@@ -31,7 +31,6 @@ export async function getUserFilesData({
msg_id: 20005,
message_body: {
action: 'search_files',
token: token,
page: page,
page_size: pageSize,
query: query,
@@ -43,7 +42,8 @@ export async function getUserFilesData({
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -77,12 +77,12 @@ export async function removeUserFile(fileId: string, fullDelete: number) {
action: 'delete_file',
file_id: fileId,
full_delete: fullDelete,
token: token
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -114,13 +114,13 @@ export async function viewFileInfo(fileId: string) {
msg_id: 20005,
message_body: {
action: 'file_info',
file_id: fileId,
token: token
file_id: fileId
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -163,3 +163,43 @@ export async function downloadFile(fileId: string, fileName: string) {
fileName: fileName
}
}
export async function FileAnAppeal(fileId: string, appealReason: string, additionalInfo: string) {
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: 20005,
message_body: {
action: 'submit_appeal',
file_id: fileId,
appeal_reason: appealReason,
additional_info: additionalInfo
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
let parsed = await response.json();
if (parsed.message_code === 0) {
return parsed;
} else {
throw parsed;
}
} else {
throw (`${response.status}`);
}
} catch (error) {
return error
}
}
+23 -15
View File
@@ -2,8 +2,6 @@
import { API_BASE_URL } from '@/app/actions/definitions';
import { getSessionData } from '@/app/actions/session';
import { FormState } from '@/app/actions/auth';
import { unknown } from 'zod';
interface initMessageBody {
file_name: string,
@@ -22,7 +20,6 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
}
const message = messageBody;
message.action = 'init';
message.token = token;
if (convertTo && convertTo !== 'reject') {
message.convertTo = convertTo;
@@ -38,7 +35,8 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -68,6 +66,8 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
}
export async function cancelUpload(udloadId: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
@@ -81,7 +81,8 @@ export async function cancelUpload(udloadId: string) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -114,7 +115,6 @@ export type ChunkResponse = {
};
export async function chunkUpload(formData: FormData, itPrivate?: boolean): Promise<ChunkResponse> {
console.log('chunkUpload');
const token = await getSessionData('token');
try {
@@ -123,19 +123,21 @@ export async function chunkUpload(formData: FormData, itPrivate?: boolean): Prom
: `${API_BASE_URL}/api/v1/files/chunk`;
if (token && itPrivate) {
/* if (token && itPrivate) {
formData.append('token', token);
}
} */
const response = await fetch(url, {
method: 'POST',
body: formData
body: formData,
headers: {
'Authorization': `Bearer ${token}`
}
});
/* ...(token && itPrivate && { 'Authorization': `Bearer ${token}` }) возможно нужно будет условно отправлять токен*/
if (response.ok) {
let parsed = await response.json();
console.log(formData);
console.log(parsed);
if (parsed.message_code === 0) {
return {
@@ -171,6 +173,8 @@ export async function chunkUpload(formData: FormData, itPrivate?: boolean): Prom
}
export async function checkChunkStatus(upload_id: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
@@ -184,7 +188,8 @@ export async function checkChunkStatus(upload_id: string) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -206,6 +211,8 @@ export async function checkChunkStatus(upload_id: string) {
}
export async function getAllowedFilesExtensions(type: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
@@ -219,7 +226,8 @@ export async function getAllowedFilesExtensions(type: string) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -250,7 +258,6 @@ export async function checkOperationPrice(action: string, filesCount: number, fi
version: 1,
msg_id: 30008,
message_body: {
auth_token: token,
action: action,
file_type: fileType,
count_files: filesCount
@@ -258,7 +265,8 @@ export async function checkOperationPrice(action: string, filesCount: number, fi
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
+13 -15
View File
@@ -14,13 +14,13 @@ export async function setMonitoring(fileId: string, monitoringType: string) {
msg_id: 30007,
message_body: {
monitoring_type: monitoringType,
file_id: fileId,
auth_token: token
file_id: fileId
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -65,14 +65,12 @@ export async function fetchMonitoringStats(): Promise<MonitoringStats | null> {
method: 'POST',
body: JSON.stringify({
version: 1,
msg_id: 30011,
message_body: {
token: token
}
msg_id: 30011
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -99,13 +97,13 @@ export async function fetchLastMonitoringCheck() {
version: 1,
msg_id: 30011,
message_body: {
token: token,
limit: 1
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -114,6 +112,8 @@ export async function fetchLastMonitoringCheck() {
if (parsed?.message_body?.searches_by_status) {
return parsed.message_body;
} else {
return null
}
}
} catch (error) {
@@ -129,14 +129,12 @@ export async function fetchMonitoringInfo() {
method: 'POST',
body: JSON.stringify({
version: 1,
msg_id: 30027,
message_body: {
authToken: token
}
msg_id: 30027
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
+11 -11
View File
@@ -8,7 +8,8 @@ export interface NotificationBody {
id: number,
message: string,
status: string | null,
type: string
type: string,
context: Record<string, string>
}
export async function fetchActiveNotifications() {
@@ -21,13 +22,13 @@ export async function fetchActiveNotifications() {
version: 1,
msg_id: 30015,
message_body: {
action: 'active',
authToken: token,
action: 'active'
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -58,7 +59,6 @@ export async function fetchAllNotifications(page: number = 0, size: number = 10)
msg_id: 30015,
message_body: {
action: 'list',
authToken: token,
page: page,
size: size,
sortBy: "createdAt",
@@ -69,7 +69,8 @@ export async function fetchAllNotifications(page: number = 0, size: number = 10)
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -100,13 +101,13 @@ export async function notificationsMarkAsRead(ids: number[]) {
msg_id: 30015,
message_body: {
action: 'markRead',
authToken: token,
notificationIds: ids
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -131,7 +132,6 @@ export async function notificationsMarkAsRead(ids: number[]) {
export async function notificationsDelete(ids: number[]) {
const token = await getSessionData('token');
console.log('notificationsDelete');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
@@ -141,13 +141,13 @@ export async function notificationsDelete(ids: number[]) {
msg_id: 30015,
message_body: {
action: 'delete',
authToken: token,
notificationIds: ids
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
+10 -17
View File
@@ -14,12 +14,12 @@ export async function fetchReferralsLevels() {
msg_id: 30003,
message_body: {
action: 'levels',
token: token,
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -57,12 +57,12 @@ export async function fetchReferralUserStats() {
msg_id: 30003,
message_body: {
action: 'userStats',
token: token,
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -96,14 +96,14 @@ export async function fetchReferralInvitees() {
msg_id: 30003,
message_body: {
action: 'invitees',
token: token,
pageSize: 100,
pageNumber: 0
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -137,25 +137,18 @@ export async function referralRefill() {
msg_id: 30003,
message_body: {
action: 'refill',
token: token,
amount: 2000,
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
console.log('referralRefill');
console.log({
action: 'refill',
token: token,
amount: 2000,
})
let parsed = await response.json();
console.log(parsed);
if (parsed.message_code === 0) {
return parsed.message_body;
@@ -366,8 +359,8 @@ export async function createPayoutRequest(
const response = await fetch(`${API_BASE_URL}/api/payouts/create-request`, {
method: 'POST',
body: JSON.stringify({
"amount": amount,
"payoutMethodId": payoutMethodId
'amount': amount,
'payoutMethodId': payoutMethodId
}),
headers: {
'Content-Type': 'application/json',
+8 -5
View File
@@ -7,8 +7,11 @@ export async function searchUserFiles(fileId: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?similarityLevels=DUPLICATE,SIMILARITY&auth_token=${token}`, {
method: 'GET'
const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?similarityLevels=DUPLICATE,SIMILARITY`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
@@ -40,8 +43,6 @@ export async function searchUserFiles(fileId: string) {
export async function searchGlobalFiles(fileId: string, currentPage: number) {
const token = await getSessionData('token');
//удалить когда поиск будет нормально работать
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST',
@@ -56,7 +57,8 @@ export async function searchGlobalFiles(fileId: string, currentPage: number) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -112,6 +114,7 @@ export async function getSearchStats() {
if (response.ok) {
let parsed = await response.json();
return parsed;
} else {
return null;
+5 -5
View File
@@ -37,7 +37,7 @@ export async function createSession(token: string, email: string) {
});
const cookieStore = await cookies();
cookieStore.set('session', session, {
cookieStore.set('session_nc_db', session, {
httpOnly: true,
secure: false,
expires: expiresAt,
@@ -48,7 +48,7 @@ export async function createSession(token: string, email: string) {
export async function updateSession(newToken: string) {
const cookieStore = await cookies();
const session = cookieStore.get('session')?.value;
const session = cookieStore.get('session_nc_db')?.value;
if (!session) {
return null;
@@ -68,7 +68,7 @@ export async function updateSession(newToken: string) {
const updatedSession = await encrypt(updatedPayload);
cookieStore.set('session', updatedSession, {
cookieStore.set('session_nc_db', updatedSession, {
httpOnly: true,
secure: false,
expires: updatedPayload.expiresAt,
@@ -79,7 +79,7 @@ export async function updateSession(newToken: string) {
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get('session')?.value;
const sessionCookie = cookieStore.get('session_nc_db')?.value;
if (!sessionCookie) {
return null;
@@ -99,7 +99,7 @@ export async function deleteSession() {
console.log('delete session')
try {
const cookieStore = await cookies()
cookieStore.delete('session');
cookieStore.delete('session_nc_db');
} catch (error) {
} finally {
+192 -4
View File
@@ -17,12 +17,12 @@ export async function requestFileWithTracking(fileId: string) {
method: 'POST',
body: JSON.stringify({
file_id: fileId,
token: token,
ip: ip,
user_agent: userAgent
}),
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -57,12 +57,12 @@ export async function requestFileAsBuffer(fileId: string): Promise<ArrayBuffer |
method: 'POST',
body: JSON.stringify({
file_id: fileId,
token: token,
ip: ip,
user_agent: userAgent
}),
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -88,3 +88,191 @@ export async function requestFileAsBuffer(fileId: string): Promise<ArrayBuffer |
return null;
}
}
export async function fetchDocumentInfo(fileId: string) {
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: 20005,
message_body: {
file_id: fileId,
action: 'file_info'
}
}),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message_code === 0) {
return parsed.message_body;
} else {
return null;
}
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
return null;
}
}
export async function filePermisionChange(fileId: string, permissions: boolean) {
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: 20005,
message_body: {
file_id: fileId,
action: 'change_permission',
permissions: {
DOWNLOAD: permissions
}
}
}),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message_code === 0) {
return true;
} else {
return false;
}
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
return null;
}
}
export async function fetchHistoryView(
page: number,
size: number,
locale: string,
sortDirection?: 'asc' | 'desc',
sortBy?: string
) {
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: 30028,
message_body: {
action: 'history_view',
page: page,
size: size,
locale: locale,
sort_direction: sortDirection,
sort_by: sortBy
}
}),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message_code === 0) {
return parsed.message_body;
} else {
return null;
}
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
return null;
}
}
export async function fetchTrackingStats() {
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: 30028,
message_body: {
action: 'view_stats'
}
}),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message_code === 0) {
return parsed.message_body;
} else {
return null;
}
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
return null;
}
}
export async function downloadTrackingFile(fileId: string, fileName?: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/files/public-download/${fileId}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw 'failed-to-download-file';
}
const arrayBuffer = await response.arrayBuffer()
const base64 = Buffer.from(arrayBuffer).toString('base64')
return {
data: base64,
contentType: response.headers.get('content-type') || 'application/octet-stream',
fileName: fileName ? fileName : 'fileName'
}
} catch (error) {
return null;
}
}
+38 -85
View File
@@ -88,13 +88,10 @@ export async function getViolationFilesArray(props: {
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<string, any> = {
token: token
};
const body: Record<string, any> = {};
if (page !== undefined) body.page = page;
if (size !== undefined) body.size = size;
@@ -107,6 +104,7 @@ export async function getViolationFilesArray(props: {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body),
});
@@ -128,42 +126,7 @@ export async function getViolationFilesArray(props: {
}
}
/* статистика нурешений */
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, status?: string, violationId?: string) {
export async function getFileMatches(fileId: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string) {
const token = await getSessionData('token');
try {
@@ -174,18 +137,19 @@ export async function getFileViolations(fileId: string, page: number, status?: s
msg_id: 30009,
message_body: {
file_id: fileId,
page: page,
page: (page > 0 ? page - 1 : 0),
size: 5,
sort_direction: 'desc',
token: token,
status: status,
...(violationId && { action: 'getByViolationId' }),
...(violationId && { violation_id: violationId })
...(violationId && { violation_id: violationId }),
...((startDate && endDate) && { start_date: startDate, end_date: endDate })
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -218,13 +182,13 @@ export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
message_body: {
group_by: group,
action: "group",
token: token,
sort_direction: 'desc'
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -244,7 +208,7 @@ export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
}
}
export async function fetchViolationStatistic() {
export async function fetchViolationStatistic(id?: string) {
const token = await getSessionData('token');
try {
@@ -254,12 +218,13 @@ export async function fetchViolationStatistic() {
version: 1,
msg_id: 30010,
message_body: {
token: token,
...(id && { file_id: id })
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -287,20 +252,17 @@ export async function fetchViolationFinalSummaryStatistic() {
method: 'POST',
body: JSON.stringify({
version: 1,
msg_id: 30023,
message_body: {
token: token,
}
msg_id: 30023
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
let parsed = await response.json();
console.log(parsed);
if (parsed?.message_code === 0) {
return parsed?.message_body;
@@ -363,7 +325,6 @@ export async function createComplaint(
version: 1,
msg_id: 30013,
message_body: {
token: token,
action: 'create',
violation_id: violationId,
text: textArea,
@@ -372,7 +333,8 @@ export async function createComplaint(
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -429,13 +391,13 @@ export async function fetchComplainInfo(id: number) {
msg_id: 30013,
message_body: {
action: 'get_by_violation',
violation_id: id,
token
violation_id: id
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -473,13 +435,13 @@ export async function updateMatchStatus(id: number, status: MatchStatus) {
message_body: {
action: 'updateStatus',
violation_id: id,
status: status,
token: token
status: status
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -499,28 +461,18 @@ export async function updateMatchStatus(id: number, status: MatchStatus) {
}
}
export async function fetchCaseInfo(id: number) {
export async function fetchClaimInfo(id: number) {
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: 30017,
message_body: {
action: 'get_byId',
token: token,
id: id
}
}), */
body: JSON.stringify({
version: 1,
msg_id: 30017,
message_body: {
action: 'get_all',
token: token,
pageSize: 10,
pageSize: 1,
pageNumber: 0,
sortBy: "createdAt",
sortDir: "desc",
@@ -529,7 +481,8 @@ export async function fetchCaseInfo(id: number) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -566,7 +519,7 @@ interface caseBody {
errorMessage?: Record<string, string> | null
}
export async function createCase(
export async function createClaim(
state: caseBody | undefined,
formData: FormData
): Promise<caseBody> {
@@ -616,7 +569,6 @@ export async function createCase(
violation_id: violationId,
description: textArea,
email: email,
token: token,
name: caseTitle,
priority: 'HIGH',
amount: Number(amount)
@@ -624,7 +576,8 @@ export async function createCase(
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -683,7 +636,7 @@ export interface ComplaintsCaseQueryParams {
sort_direction?: 'desc' | 'asc'
}
export async function fetchLastCase(params: ComplaintsCaseQueryParams) {
export async function fetchLastClaims(params: ComplaintsCaseQueryParams) {
const token = await getSessionData('token');
const { page, size, sort_by, sort_direction } = params;
@@ -695,8 +648,7 @@ export async function fetchLastCase(params: ComplaintsCaseQueryParams) {
msg_id: 30017,
message_body: {
action: "get_all",
token: token,
page,
pageNumber: page,
size,
sort_by,
sort_direction
@@ -704,7 +656,8 @@ export async function fetchLastCase(params: ComplaintsCaseQueryParams) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
@@ -736,7 +689,6 @@ export async function fetchLastComplaint(params: ComplaintsCaseQueryParams) {
msg_id: 30013,
message_body: {
action: "get_all",
token: token,
page,
size,
sort_by,
@@ -745,7 +697,8 @@ export async function fetchLastComplaint(params: ComplaintsCaseQueryParams) {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
+8 -2
View File
@@ -8,6 +8,7 @@ const checkout = new YooCheckout({ shopId: '1276731', secretKey: 'test_0Yns_0NHV
export async function makePaymentOperation(email: string, tariffId: number, operationUuid: string, operationType: 'TARIFF' | 'TOKEN') {
const formData = new FormData();
const token = await getSessionData('token');
formData.append('email', email);
formData.append('tariffId', tariffId.toString());
@@ -17,7 +18,10 @@ export async function makePaymentOperation(email: string, tariffId: number, oper
try {
const response = await fetch(`${API_BASE_URL}/api/payments/create`, {
method: 'POST',
body: formData
body: formData,
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
@@ -79,6 +83,7 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera
export async function fetchPaymentsHistory() {
const email = await getSessionData('email');
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
@@ -93,7 +98,8 @@ export async function fetchPaymentsHistory() {
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
-38
View File
@@ -1,38 +0,0 @@
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { code } = await request.json();
try {
const response = await fetch('https://oauth.yandex.ru/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
client_id: 'b356a7ceac0f4c50a3b434220f86bce0',
client_secret: '0800f7774f7d4e19ad4b434f10afd4ab',
})
});
const data = await response.json();
console.log(data);
if (data.access_token) {
const userInfo = await fetch('https://login.yandex.ru/info?format=json', {
headers: { 'Authorization': `OAuth ${data.access_token}` }
}).then(r => r.json());
console.log(userInfo);
// Тут мне нужно будет отправить сделать запрос в бек с userInfo и там уже редиректить на дешборд
return NextResponse.json({ success: true });
}
} catch (error) {
console.error('Token exchange failed:', error);
}
return NextResponse.json({ success: false }, { status: 400 });
}
@@ -0,0 +1,103 @@
'use client'
import { useTranslations } from 'next-intl'
interface NotificationTextHandlerProps {
notificationType: string
notificationMessage: string
notificationContext: Record<string, string>
}
export function NotificationTextHandler({
notificationType,
notificationMessage,
notificationContext
}: NotificationTextHandlerProps) {
const t = useTranslations('Notifications');
const getNotificationText = () => {
if (!notificationType) {
return t.has(notificationMessage) ? t(notificationMessage) : notificationMessage
}
switch (notificationType) {
case 'USER_VERIFIED':
return t('user-verified', {
status: notificationContext?.status || t('not-specified'),
updateDate: notificationContext?.update_date || t('not-specified-date')
});
case 'MONITORING_RESULT':
return t('monitoring-result', {
message: notificationContext?.message || t('message-missing')
});
case 'COMPLAINT_STATUS_CHANGED':
return t('complaint-status-changed', {
newStatus: notificationContext?.new_status || t('not-specified'),
oldStatus: notificationContext?.old_status || t('not-specified'),
violation: notificationContext?.violation || t('not-specified'),
file: notificationContext?.file || t('not-specified')
});
case 'FILE_ADDED_TO_SYSTEM':
return t('file-added-to-system', {
status: notificationContext?.status || t('not-specified'),
fileName: notificationContext?.file_name || t('not-specified-file-name')
});
case 'FILE_MODERATION_EVENT':
return t('file-moderation-event', {
fileName: notificationContext?.file_name || t('not-specified-file-name'),
oldStatus: notificationContext?.old_status || t('not-specified'),
newStatus: notificationContext?.new_status || t('not-specified'),
comment: notificationContext?.comment || t('no-comment')
});
case 'PAYMENT_RESULT':
return t('payment-result', {
status: notificationContext?.status || t('not-specified'),
amount: notificationContext?.amount || '0'
});
case 'REFERRAL_REGISTERED':
return t('referral-registered', {
referralUser: notificationContext?.referral_user || t('unknown-user')
});
case 'SEARCH_RESULT':
return t('search-result', {
startDate: notificationContext?.start_date || t('not-specified-date'),
endDate: notificationContext?.end_date || t('not-specified-date'),
searchType: notificationContext?.search_type || t('not-specified'),
count: notificationContext?.count || '0'
});
case 'SEARCH_OPERATION_FAILED':
return t('search-operation-failed', {
status: notificationContext?.status || t('not-specified'),
errorMessage: notificationContext?.error_message || t('error-not-described'),
message: notificationContext?.message || t('message-missing'),
reason: notificationContext?.reason || t('reason-not-specified'),
nextRun: notificationContext?.next_run || t('not-scheduled')
});
case 'TOKEN_NOT_FOUND':
return t('token-not-found', {
currentTokens: notificationContext?.current_tokens || '0',
needTokens: notificationContext?.need_tokens || t('not-specified'),
message: notificationContext?.message || t('message-missing'),
nextRun: notificationContext?.next_run || t('not-scheduled')
});
default:
return notificationMessage || t('notification-without-text');
}
};
return (
<div>
{getNotificationText()}
</div>
);
}
+8 -5
View File
@@ -2,13 +2,15 @@
import { useEffect, useRef, useState } from 'react';
import { usePdf } from '@/app/contexts/PdfContext';
import { requestFileAsBuffer } from '@/app/actions/trackingActions';
interface PdfViewerProps {
fileId: string;
fileName?: string;
showDownload?: boolean;
}
export default function PdfViewer({ fileId, fileName = 'document.pdf' }: PdfViewerProps) {
export default function PdfViewer({ fileId, fileName = 'document.pdf', showDownload }: PdfViewerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
@@ -26,16 +28,17 @@ export default function PdfViewer({ fileId, fileName = 'document.pdf' }: PdfView
const loadAndDisplayPdf = async () => {
try {
setLoading(true);
// 1. Получаем данные документа как ArrayBuffer (а не blob URL)
const { requestFileAsBuffer } = await import('@/app/actions/trackingActions');
const pdfData = await requestFileAsBuffer(fileId);
if (!pdfData) {
throw new Error('Failed to load document');
}
setPdfData(pdfData, fileName);
//это нужно для скачивания так-как иначе pdfData теряется для скачивания
if (showDownload) {
const pdfDataForDownload = pdfData.slice(0);
setPdfData(pdfDataForDownload, fileName);
}
if (!isMounted) return;
@@ -1,12 +1,49 @@
'use client';
import { usePdf } from '@/app/contexts/PdfContext';
import { downloadTrackingFile } from '@/app/actions/trackingActions';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
interface DownloadButtonProps {
fileId: string;
fileName?: string;
}
export function DownloadButton() {
const { downloadPdf } = usePdf();
export function DownloadButton({ fileId, fileName }: DownloadButtonProps) {
const t = useTranslations('Global');
const handleDownload = async () => {
try {
const result = await downloadTrackingFile(fileId, fileName);
if (!result) {
throw 'failed-to-download-file'
}
const byteCharacters = atob(result.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: result.contentType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
toast.success(`${t('file-is-downloading')} - `);
} catch (error) {
toast.error(t('failed-to-download-file'));
}
};
return (
<button className="btn btn-out" onClick={downloadPdf}>
<button className="btn btn-out" onClick={handleDownload}>
Скачать документ
</button>
);
@@ -0,0 +1,18 @@
'use client';
import Link from 'next/link';
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
export function LoginUserBage() {
const { data: userData, isLoading, isError, error } = useUserProfile();
if (!userData?.fullName) {
return null;
}
return (
<Link href="#" className="header-user">
{userData?.fullName}
</Link>
);
}
+125 -20
View File
@@ -32,6 +32,8 @@ import { useDebouncedCallback } from 'use-debounce';
import Link from 'next/link';
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
import Image from 'next/image';
import { filePermisionChange } from '@/app/actions/trackingActions';
import {FileAppealModalWindow} from '@/app/ui/modal-windows/file-appeal-modal-window';
export type FileItem = {
id: string;
@@ -40,12 +42,15 @@ export type FileItem = {
violations?: number | undefined;
size?: number | undefined;
uploadDate?: number;
status?: string;
status: string;
monitoring: string;
protectStatus: string;
_original?: ApiFile;
supportId: number;
thumbnailFileUrl: string
thumbnailFileUrl: string;
permissions: {
DOWNLOAD: boolean;
}
};
type ApiFile = {
@@ -61,6 +66,9 @@ type ApiFile = {
supportId: number;
fileName: string;
thumbnailFileUrl: string;
permissions: {
DOWNLOAD: boolean;
}
};
type ApiResponse = {
@@ -190,7 +198,10 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
protectStatus: item.protectStatus,
supportId: item.supportId,
_original: item,
thumbnailFileUrl: item.thumbnailFileUrl
thumbnailFileUrl: item.thumbnailFileUrl,
permissions: {
DOWNLOAD: item.permissions.DOWNLOAD,
}
}));
return {
@@ -285,6 +296,32 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
});
}, [apiResponse]);
const StatusBadge = ({ status, protectStatus }: { status: string, protectStatus: string }) => {
switch (status) {
case 'ACTIVE':
return <span className="table-item-status-badge protected">
{protectStatus ? t(protectStatus) : t('error')}
</span>;
case 'MODERATION':
return <span className="table-item-status-badge moderation">
{t('pending-moderation')}
</span>;
case 'BLOCKED':
return <span className="table-item-status-badge blocked">
{t('blocked')}
</span>;
case 'REMOVED':
return <span className="table-item-status-badge blocked">
{t('file-has-been-deleted')}
</span>;
default:
return <span className="table-item-status-badge protected">
{protectStatus ? t(protectStatus) : t('error')}
</span>;
}
}
const isAllSelected = useMemo(() => {
if (!apiResponse?.items?.length) return false;
const allIds = new Set(apiResponse.items.map(item => item.id));
@@ -421,9 +458,7 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
{cutFileName(row.original.fileName)}
</span>
<br />
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <span className="table-item-protected">
{row.original?.protectStatus ? t(row.original?.protectStatus) : t('error')}
</span>
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <StatusBadge status={row.original.status} protectStatus={row.original.protectStatus} />
</div>
</div>
</div>
@@ -553,6 +588,33 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
)
},
},
{
accessorKey: 'downloadPermision',
header: ({ column }) => (
<div className="column">
<span>
Можно скачать по ссылке
</span>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center font-semibold table-item">
<button
onClick={() => changeDownloadPermition(row.original.id, row.original.permissions.DOWNLOAD)}
disabled={isFileLoading}
className={`toggle-switch ${row.original.permissions?.DOWNLOAD ? 'active' : 'inactive'}`}
title={'change-permission'}
>
<span className="toggle-slider"></span>
<span className="toggle-label">
{row.original.permissions?.DOWNLOAD ? 'Да' : 'Нет'}
</span>
</button>
</div>
)
},
},
{
id: 'actions',
header: () => {
@@ -564,15 +626,8 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
},
cell: ({ row }) => (
<div className="actions">
{(showFileLink && row.original.fileType === 'document') ? (
<div className="actions-group">
<Link
href={`/pages/file/${row.original.id}`}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
<IconEye />
</Link>
{(showFileLink && row.original.fileType === 'document') && (
<Link
href={`/watch-doc?docid=${row.original.id}`}
className="bg-indigo-500 hover:bg-indigo-600"
@@ -581,7 +636,20 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
>
<IconLink />
</Link>
)}
</div>
) : (
<>
{/* "BLOCKED" "REMOVED" */}
{(row.original.status !== 'BLOCKED' && row.original.status !== 'REMOVED') ? (
<>
<div className="actions-group">
<Link
href={`/pages/file/${row.original.id}`}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
<IconEye />
</Link>
</div>
<div className="actions-group">
{row.original.protectStatus === 'PROTECTED' && (
@@ -604,7 +672,22 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
<IconInfo />
</button>
</div>
</>
) : (
<div className="actions-group">
<button
onClick={() => {
handleAppeal(row.original)
}}
className="table-action-appeal"
title={t('file-an-appeal')}
>
<IconInfo />
</button>
</div>
)}
</>
)}
</div>
),
enableSorting: false,
@@ -625,6 +708,17 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
setOpenWindow(true);
};
const handleAppeal = async (file: FileItem) => {
console.log(file);
setOpenWindowChildren(() => {
return (
<FileAppealModalWindow fileId={file.id} setWindowClose={setOpenWindow} setWindowChildren={setOpenWindowChildren} />
)
})
setOpenWindow(true);
};
const handleDownload = async (file: FileItem) => {
setIsFileLoading(true)
@@ -659,6 +753,16 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
}
}
const changeDownloadPermition = async (fileId: string, permission: boolean) => {
const response = await filePermisionChange(fileId, !permission);
if (response) {
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
toast.success('Разрешение на скачивание изменено.');
} else {
toast.warning('Не удалось изменить разрешение на скачивание.');
}
}
// Фильтрация по типу файла и дате
const filteredData = useMemo(() => {
let result = tableData;
@@ -735,6 +839,10 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
state: {
sorting,
pagination,
columnVisibility: {
monitoring: showFileLink ? false : true,
downloadPermision: showFileLink ? true : false,
}
},
pageCount: totalPages,
manualPagination: true,
@@ -959,10 +1067,7 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
{t('page')}{' '}
<strong>
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {tableData.length} {t('out-of')} {totalItems} {pluralizeFiles(totalItems)}
</strong> | {t('total-files')}: {totalItems}
</span>
</div>
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { getFileViolations } from '@/app/actions/violationActions';
import { getFileMatches } from '@/app/actions/violationActions';
export interface ViolationFileDetail {
id: number;
@@ -24,11 +24,11 @@ interface ViolationFile {
has_previous: boolean;
}
export const useFileViolations = (id: string, page: number, status?: string, violationId?: string) => {
export const useFileViolations = (id: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string,) => {
return useQuery({
queryKey: ['fileViolations', id, page, status, violationId],
queryKey: ['fileViolations', id, page, status, violationId, startDate, endDate],
queryFn: () => {
return getFileViolations(id, page, status, violationId)
return getFileMatches(id, page, status, violationId, startDate, endDate)
},
select: (data: ViolationFile | null) => {
if (!data) {
@@ -0,0 +1,44 @@
import { useQuery } from '@tanstack/react-query';
import { fetchHistoryView } from '@/app/actions/trackingActions';
export interface HistoryViewItem {
city: string;
country: string;
viewer: string;
document: string;
view_date: string;
}
export interface UseHistoryView {
documentsCount: number;
history: HistoryViewItem[];
pageNumber: number;
pageSize: number;
totalElements: number;
totalPages: number;
uniqueCityCount: number;
uniqueCountryCount: number;
}
export const useHistoryView = (
page: number,
size: number,
locale: string,
sortDirection?: 'asc' | 'desc',
sortBy?: string
) => {
return useQuery({
queryKey: ['violationStatistic', page, size, locale, sortDirection, sortBy],
queryFn: () => {
return fetchHistoryView(page, size, locale, sortDirection, sortBy)
},
select: (data: UseHistoryView | null) => {
if (!data) {
return null
}
return data;
},
retry: false,
placeholderData: (previousData) => previousData // для оптимистичных обновлений
});
};
+2 -2
View File
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { fetchLastCase, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
import { fetchLastClaims, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
export interface Case {
amount: number;
@@ -31,7 +31,7 @@ export const useLastCases = (params: ComplaintsCaseQueryParams) => {
return useQuery({
queryKey: ['lastCases', params.page, params.size, params.sort_by, params.sort_direction],
queryFn: () => {
return fetchLastCase(params)
return fetchLastClaims(params)
},
select: (data: useLastCase | null) => {
if (!data) {
@@ -0,0 +1,29 @@
import { useQuery } from '@tanstack/react-query';
import { fetchTrackingStats } from '@/app/actions/trackingActions';
export interface useTrackingStats {
totalViews: number,
uniqueIps: number,
uniqueUsers: number,
documentsCount: number,
uniqueCountryCount: number,
uniqueCityCount: number,
firstViewDate: string,
lastViewDate: string
}
export const useTrackingStats = () => {
return useQuery({
queryKey: ['trackingStats'],
queryFn: () => {
return fetchTrackingStats()
},
select: (data: useTrackingStats | null) => {
if (!data) {
return null
}
return data;
},
retry: false
});
};
@@ -5,14 +5,16 @@ export interface UseViolationStatistic {
total_violations: number,
new_violations: number,
in_progress_violations: number,
resolved_violations: number
resolved_violations: number,
total_complaints: number,
total_law_cases: number
}
export const useViolationStatistic = () => {
export const useViolationStatistic = (id?: string) => {
return useQuery({
queryKey: ['violationStatistic'],
queryKey: ['violationStatistic', id],
queryFn: () => {
return fetchViolationStatistic()
return fetchViolationStatistic(id)
},
select: (data: UseViolationStatistic | null) => {
if (!data) {
+11
View File
@@ -0,0 +1,11 @@
'use client';
import Providers from './getQueryServer';
export function WatchDocProviders({ children }: { children: React.ReactNode }) {
return (
<Providers>
{children}
</Providers>
);
}
-23
View File
@@ -1,34 +1,11 @@
'use client'
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { getQueryClient } from '@/app/providers/getQueryClient';
/* export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (isServer) {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
} */
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
+13 -2
View File
@@ -163,7 +163,7 @@
.btn,
.btn-s,
.btn-m {
.btn-l {
border: none;
cursor: pointer;
font-weight: 600;
@@ -197,7 +197,8 @@
}
.btn-primary {
background: linear-gradient(135deg, v.$p-color, v.$s-color);
/* background: linear-gradient(135deg, v.$p-color, v.$s-color); */
background: #6366f1;
color: v.$white;
border: 2px solid transparent;
@@ -222,6 +223,16 @@
}
}
.btn-cancel {
background: v.$bg-hover;
color: v.$text-p;
border: 2px solid v.$border-color-1;
&:hover {
transform: translateY(-2px);
}
}
.btn-outline {
background: transparent;
border: 2px solid v.$p-color;
+4 -4
View File
@@ -270,16 +270,16 @@
position: absolute;
right: -5px;
bottom: -5px;
width: 20px;
height: 20px;
border-radius: 50%;
/* width: 22px;
height: 22px; */
border-radius: 15px;
background: v.$red;
color: v.$white;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
padding-top: 1px;
padding: 2px 5px;
}
}
+168 -49
View File
@@ -650,6 +650,10 @@
&.text-filter-search {
flex-grow: 1;
justify-content: space-between;
&.end {
justify-content: end;
}
}
@media (max-width: 768px) {
@@ -926,13 +930,24 @@
}
}
.table-item-protected {
color: #10b981;
.table-item-status-badge {
font-size: 12px;
position: relative;
margin-left: 5px;
padding-left: 5px;
&.protected {
color: #10b981;
}
&.moderation {
color: v.$color-warning;
}
&.blocked {
color: v.$red;
}
&::before {
content: '';
color: v.$text-s;
@@ -1012,11 +1027,11 @@
}
}
.table-action-delete {
background-color: #e80a14;
.table-action-appeal {
background-color: #9f0712;
&:hover {
background-color: #9f0712;
background-color: v.$red;
}
}
@@ -1027,6 +1042,67 @@
background-color: #155dfc;
}
}
.toggle-switch {
position: relative;
display: inline-flex;
align-items: center;
justify-content: space-between;
width: 60px;
height: 28px;
padding: 0 6px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 12px;
font-weight: 500;
background-color: #615fff;
padding: 0;
}
.toggle-switch.active {
justify-content: flex-end;
}
.toggle-switch.inactive {
justify-content: flex-start;
}
.toggle-switch .toggle-slider {
position: absolute;
width: 30px;
height: 28px;
background-color: white;
border: 1px solid #615fff;
border-radius: 8px;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.toggle-switch.active .toggle-slider {
transform: translateX(0);
}
.toggle-switch.inactive .toggle-slider {
transform: translateX(0);
}
.toggle-switch .toggle-label {
z-index: 1;
color: v.$text-p;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
width: 30px;
height: 26px;
display: flex;
justify-content: center;
align-items: center;
}
.toggle-switch:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.table-item-checkbox {
@@ -2738,7 +2814,6 @@
.cases-carousel-track {
position: relative;
margin-bottom: 16px;
.swiper-slide {
height: auto;
@@ -2777,6 +2852,14 @@
.swiper-pagination-bullet-active {
background: #3b82f6 !important;
}
.swiper-pagination-fraction,
.swiper-pagination-custom,
.swiper-horizontal>.swiper-pagination-bullets,
.swiper-pagination-bullets.swiper-pagination-horizontal {
margin-top: 16px;
position: static;
}
}
.violation-check-all-section {
@@ -3727,7 +3810,7 @@
}
.info-header {
background: linear-gradient(0deg, #6366f1 50%, #8b5cf6 130%);
background: #6366f1;
color: white;
padding: 16px 20px;
border-bottom: 1px solid v.$border-color-1;
@@ -3775,6 +3858,84 @@
padding: 12px 20px;
border-bottom: 1px solid #f1f5f9;
}
.appeal-form {
margin-top: 20px;
}
.appeal-textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: inherit;
font-size: 14px;
resize: vertical;
}
.appeal-textarea:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}
.appeal-textarea:disabled {
background-color: #f3f4f6;
cursor: not-allowed;
}
.required {
color: #ef4444;
}
.appeal-actions {
display: flex;
gap: 12px;
justify-content: center;
margin: 10px 0;
}
.appeal-submit-btn {
padding: 8px 20px;
background-color: #6366f1;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s;
}
.appeal-submit-btn:hover:not(:disabled) {
background-color: #4f46e5;
}
.appeal-submit-btn:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
.appeal-cancel-btn {
padding: 8px 20px;
background-color: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.appeal-cancel-btn:hover:not(:disabled) {
background-color: #e5e7eb;
}
.appeal-cancel-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
}
@@ -3820,48 +3981,6 @@
}
}
.section-add-file {
position: relative;
.section-drop-down-list {
position: absolute;
left: 80%;
top: 50%;
max-height: 0;
color: v.$text-p;
font-size: 16px;
border-radius: 20px;
padding: 0;
border: none;
width: fit-content;
z-index: 2;
font-weight: 500;
background: #ffffff00;
overflow: hidden;
transition: all 0.5s ease;
transform: translateY(-50%);
&.opened {
max-height: 500px;
padding: 15px 10px;
box-shadow: 0 4px 20px #00000014;
background: v.$white;
}
a {
white-space: nowrap;
color: v.$p-color;
opacity: 0.7;
}
a:hover {
transition: all 0.3s ease;
opacity: 1;
}
}
}
.violations-table {
background: white;
border-radius: 20px;
+5 -1
View File
@@ -164,6 +164,10 @@
justify-content: center;
}
}
.error-container {
text-align: center;
}
}
&-page {
@@ -247,9 +251,9 @@
background: var(--card);
border: 1px solid var(--border);
border-radius: 14px;
margin-bottom: 14px;
overflow: hidden;
box-shadow: var(--shadow);
height: 100%;
.card-head {
padding: 14px 20px;
@@ -2,11 +2,9 @@
import { useQuery } from '@tanstack/react-query';
import { getUserFilesData } from '@/app/actions/fileEntity';
import { useEffect, useState, useRef } from 'react';
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
import { convertBytes } from '@/app/lib/convertBytes';
import { useTranslations } from 'next-intl';
import { useClickOutside } from '@/app/hooks/useClickOutside';
import Link from 'next/link';
type FileItem = {
@@ -71,15 +69,6 @@ export default function DashboardUserFiles() {
});
const t = useTranslations('Global');
const [openDropDownList, setOpenDropDownList] = useState(false);
const dropDownList = useRef(null);
useClickOutside(
dropDownList,
() => {
setOpenDropDownList(false)
},
openDropDownList
);
const formatDate = (timestamp: number) => {
const date = new Date(timestamp);
@@ -97,30 +86,11 @@ export default function DashboardUserFiles() {
{t('your-files')}
</h3>
<div
className="section-add-file btn view-all-link"
onClick={() => {
setOpenDropDownList(!openDropDownList);
}}
ref={dropDownList}
className="btn view-all-link"
>
{t('add')}
<div
className={`section-drop-down-list ${openDropDownList ? 'opened' : ''}`}
>
<div
className="flex flex-col text-center"
>
<Link href='/pages/marking-images'>
{t('photo-marking')}
<Link href='/pages/my-content'>
{t('all')}
</Link>
<Link href='/pages/marking-video'>
{t('video-marking')}
</Link>
<Link href='/pages/marking-audio'>
{t('audio-marking')}
</Link>
</div>
</div>
</div>
</div>
@@ -62,7 +62,7 @@ export default function DashboardUserViolations() {
{t('violation')}
</h3>
<div
className="section-add-file btn view-all-link"
className="btn view-all-link"
>
<Link href='/pages/violations'>
{t('all')}
@@ -82,7 +82,7 @@ export default function DashboardUserViolations() {
</div>
)}
{violationData?.content.length ? (
{violationData?.content?.length ? (
violationData?.content.map((file) => {
return (
<div
@@ -1,4 +1,10 @@
export default function FilePpageFileStatistic() {
'use client'
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
export default function FilePageFileStatistic({ id }: { id: string }) {
const { data: violationStatistic } = useViolationStatistic(id);
return (
<div
className="block-wrapper"
@@ -13,36 +19,36 @@ export default function FilePpageFileStatistic() {
<div className="stat-mini-card">
<div
className="stat-mini-value"
style={{'color': '#dc2626'}}
style={{ 'color': '#dc2626' }}
>
0
{violationStatistic?.new_violations ? violationStatistic?.new_violations : 0}
</div>
<div className="stat-mini-label">Новых</div>
</div>
<div className="stat-mini-card">
<div
className="stat-mini-value"
style={{'color': '#d97706'}}
style={{ 'color': '#d97706' }}
>
0
{violationStatistic?.in_progress_violations ? violationStatistic?.in_progress_violations : 0}
</div>
<div className="stat-mini-label">В работе</div>
</div>
<div className="stat-mini-card">
<div
className="stat-mini-value"
style={{'color': '#059669'}}
style={{ 'color': '#059669' }}
>
0
{violationStatistic?.resolved_violations ? violationStatistic?.resolved_violations : 0}
</div>
<div className="stat-mini-label">Решено</div>
</div>
<div className="stat-mini-card">
<div
className="stat-mini-value"
style={{'color': '#6366f1'}}
style={{ 'color': '#6366f1' }}
>
0
{violationStatistic?.total_violations ? violationStatistic?.total_violations : 0}
</div>
<div className="stat-mini-label">Всего</div>
</div>
@@ -2,7 +2,7 @@
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
import { useEffect, useActionState, SetStateAction } from 'react';
import { fetchCaseInfo, createCase, MatchStatus } from '@/app/actions/violationActions';
import { fetchClaimInfo, createClaim, MatchStatus } from '@/app/actions/violationActions';
import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
@@ -37,11 +37,11 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler,
}
) {
const t = useTranslations('Global');
const [state, formAction, isPending] = useActionState(createCase, undefined);
const [state, formAction, isPending] = useActionState(createClaim, undefined);
const { data: caseInfo, isLoading: isLoadingCase } = useQuery({
queryKey: ['caseInfo', selectedViolation.id],
queryFn: () => fetchCaseInfo(selectedViolation.id),
queryFn: () => fetchClaimInfo(selectedViolation.id),
select: (data) => {
if (data) {
return data
@@ -52,7 +52,7 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler,
useEffect(() => {
if (state?.success) {
toast.success(t('the-complaint-has-been-registered'));
toast.success(t('the-claims-has-been-registered'));
const newStatus = selectedViolation.status === 'COMPLAINT_IN_WORK'
? 'COMPLAINT_AND_LEGAL_IN_WORK'
@@ -69,7 +69,7 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler,
if (state.errorMessage?.server) {
toast.warning(t(state.errorMessage.server));
} else {
toast.warning(t('the-complaint-has-not-been-registered'));
toast.warning(t('the-claims-has-not-been-registered'));
}
}
}, [state, updateStatusHandler, selectedViolation.id]);
@@ -1,7 +1,7 @@
'use client'
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
import {useActionState, useEffect, useState, SetStateAction} from 'react';
import { useActionState, useEffect, useState, SetStateAction } from 'react';
import { useTranslations } from 'next-intl';
import { createComplaint, fetchComplainInfo, MatchStatus } from '@/app/actions/violationActions';
import { toast } from 'sonner';
@@ -350,60 +350,48 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler,
return (
<div className="violation-info-case">
{complainInfo?.body?.content ? (
<>
{complainInfo.body.content.map((item: complaintInfo) => {
return (
<div key={item.id}>
{complainInfo?.body ? (
<div>
<div className="complaint-details">
<div className="complaint-details-item">
<div className="complaint-details-header">
<h5>COM-{item.id}</h5>
<h5>COM-{complainInfo.body?.id}</h5>
</div>
</div>
<div className="complaint-details-item">
<div className="complaint-details-title">{t('Status')}:</div>
<div
className="complaint-details-content"
>
{item.status}
<div className="complaint-details-content">
{complainInfo.body?.status}
</div>
</div>
<div className="complaint-details-item">
<div className="complaint-details-title">{t('date-of-creation')}:</div>
<div
className="complaint-details-content"
>
{item.created_at ? `${formatDate(item.created_at)}: ${formatDateTime(item.created_at)}` : '---'}
<div className="complaint-details-content">
{complainInfo.body?.created_at
? `${formatDate(complainInfo.body.created_at)}: ${formatDateTime(complainInfo.body.created_at)}`
: '---'}
</div>
</div>
<div className="complaint-details-item">
<div className="complaint-details-title">{t('date-of-update')}:</div>
<div
className="complaint-details-content"
>
{item.updated_at ? `${formatDate(item.updated_at)}: ${formatDateTime(item.updated_at)}` : '---'}
<div className="complaint-details-content">
{complainInfo.body?.updated_at
? `${formatDate(complainInfo.body.updated_at)}: ${formatDateTime(complainInfo.body.updated_at)}`
: '---'}
</div>
</div>
<div className="complaint-details-item">
<div className="complaint-details-title">{t('text-of-the-complaint')}:
</div>
<div
className="complaint-details-content"
>
{item.complaint_text}
<div className="complaint-details-title">{t('text-of-the-complaint')}:</div>
<div className="complaint-details-content">
{complainInfo.body?.complaint_text}
</div>
</div>
</div>
</div>
);
})}
</>
) : (
<div>
{t('no-information-about-the-complaint')}
@@ -17,13 +17,17 @@ export default function FilePageViolationsList({
fileId,
status,
violationId,
caseType
caseType,
startDate,
endDate
}: {
currentPage: number,
fileId: string,
status: string | undefined,
violationId?: string | undefined
caseType?: 'claim' | 'complaint'
violationId?: string | undefined,
caseType?: 'claim' | 'complaint',
startDate?: string,
endDate?: string
}) {
const t = useTranslations('Global');
const tStatus = useTranslations('Match-status');
@@ -38,7 +42,7 @@ export default function FilePageViolationsList({
current_page: number;
} | null>(null);
const { data: fileViolations, error, isPending } = useFileViolations(fileId, currentPage, status, violationId);
const { data: fileViolations, error, isPending } = useFileViolations(fileId, currentPage, status, violationId, startDate, endDate);
function selectHandler(violation: ViolationFileDetail) {
setSelectedViolation(violation);
@@ -228,7 +232,7 @@ export default function FilePageViolationsList({
className="sources-list-pagination"
>
<span>
{t('total-violations')}: {fileViolations?.total_elements} | {t('page')} {fileViolations?.current_page} {t('out-of')} {fileViolations?.total_pages}
{t('page')} <strong>{fileViolations?.current_page + 1} {t('out-of')} {fileViolations?.total_pages}</strong> | {t('total-violations')}: {fileViolations?.total_elements}
</span>
{fileViolations?.total_pages > 1 && (
<div className="pagination-controls">
+7 -2
View File
@@ -256,9 +256,12 @@ export default function RegisterForm() {
<ConfirmMailModalWindow userId={state?.userId} email={formState?.previousState?.email} onClose={() => { setShowMailConfirmWindow(false) }} />
</div>
)}
<form action={(e) => {
<form
action={(e) => {
formAction(e);
}}>
}}
autoComplete="off"
>
<div className={`${styles['form-switcher']}`}>
<button
className={`${styles['form-switcher-button']} ${accountType === 'b2c' ? styles['active'] : ''}`}
@@ -432,6 +435,7 @@ export default function RegisterForm() {
e.target.value = e.target.value.toLocaleLowerCase();
onChangeHandler(e)
}}
autoComplete="off"
/>
{formState?.error?.email && (
<p className="text-sm text-red-500">
@@ -486,6 +490,7 @@ export default function RegisterForm() {
placeholder={t('password-placeholder')}
defaultValue={formState?.previousState?.password ? formState?.previousState?.password : ''}
onChange={onChangeHandler}
autoComplete="off"
/>
<button
onClick={() => {
+7 -6
View File
@@ -6,9 +6,10 @@ import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { IconInfoMessage, IconPolicy, IconFiles, IconAccountBalanceWallet, IconCheckbook, IconGavel } from '@/app/ui/icons/icons';
import { useActiveNotifications } from '@/app/hooks/react-query/useActiveNotificationsList';
import { NotificationBody, notificationsMarkAsRead } from '@/app/actions/notificationActions';
import { notificationsMarkAsRead } from '@/app/actions/notificationActions';
import { useQueryClient } from '@tanstack/react-query';
import { formatDate } from '@/app/lib/formatDate';
import { NotificationTextHandler } from '@/app/components/NotificationTextHandler';
export default function NotificationsButton() {
const t = useTranslations('Global');
@@ -147,12 +148,12 @@ export default function NotificationsButton() {
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: "20px", height: "20px" }}>
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path>
</svg>
{notificationList?.unreadCount !== 0 && (
{(notificationList?.unreadCount !== 0 && notificationList?.unreadCount !== undefined) && (
<span
className="notification-unread-count"
style={{ fontSize: getFontSizeByCount(notificationList?.unreadCount) }}
/* style={{ fontSize: getFontSizeByCount(notificationList?.unreadCount) }} */
>
{notificationList?.unreadCount ? notificationList?.unreadCount : ''}
{notificationList?.unreadCount > 9 ? '9+' : notificationList?.unreadCount}
</span>
)}
</button>
@@ -220,7 +221,7 @@ export default function NotificationsButton() {
className="notification-item-header"
>
<div className="notification-title">
{norificationTranslate.has(item.type) ? norificationTranslate(item.type) : item.type}
{norificationTranslate.has(item.message) ? norificationTranslate(item.message) : item.message}
</div>
<div className="notification-data">
@@ -229,7 +230,7 @@ export default function NotificationsButton() {
</div>
<div className="notification-text">
{norificationTranslate.has(item.message) ? norificationTranslate(item.message) : item.message}
<NotificationTextHandler notificationType={item.type} notificationMessage={item.message} notificationContext={item.context} />
</div>
+4 -1
View File
@@ -7,6 +7,7 @@ import { logout } from '@/app/actions/auth';
import { useTranslations } from 'next-intl';
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
import { useQueryClient } from '@tanstack/react-query';
import {formatDate} from '@/app/lib/formatDate';
export default function UserMenuButton() {
const [isOpen, setIsOpen] = useState(false);
@@ -62,14 +63,16 @@ export default function UserMenuButton() {
<div className="user-email">
{userData?.email ?? ''}
</div>
{userData?.tariffInfo?.tariffName && (
<div className="user-subscription">
<span className="text-[14px]">{t('rate')}: </span>
<span className="subscription-badge">
{userData?.tariffInfo?.tariffName ? userData?.tariffInfo?.tariffName : '---'}
</span>
<br />
<span className="user-subscription-expire">{t('expires')}: 00.00.00</span>
<span className="user-subscription-expire">{t('expires')}: {formatDate(userData?.tariffInfo.endTariff)}</span>
</div>
)}
</div>
</div>
+8
View File
@@ -283,3 +283,11 @@ export function IconGlobe() {
</svg>
)
}
export function IconCity() {
return (
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon">
<path d="M120-120v-560h240v-80l120-120 120 120v240h240v400H120Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm240 320h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm240 480h80v-80h-80v80Zm0-160h80v-80h-80v80Z" />
</svg>
)
}
@@ -0,0 +1,107 @@
'use client';
import { convertBytes } from '@/app/lib/convertBytes';
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import { useTranslations } from 'next-intl';
import { FileDetails } from '@/app/[locale]/pages/file/[id]/page';
import { FileAnAppeal } from '@/app/actions/fileEntity';
import { useState } from 'react';
import { toast } from 'sonner';
export function FileAppealModalWindow({ fileId, setWindowClose, setWindowChildren }: {
fileId: string,
setWindowClose: any,
setWindowChildren: any
}) {
const t = useTranslations('Global');
const [appealReason, setAppealReason] = useState('');
const [additionalInfo, setAdditionalInfo] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmitAppeal = async () => {
if (!appealReason.trim()) {
toast.error(t('please-enter-appeal-reason'));
return;
}
setIsSubmitting(true);
try {
const result = await FileAnAppeal(fileId, appealReason, additionalInfo);
if (result?.message_desc === 'Appeal submitted successfully') {
toast.success(t('appeal-submitted-successfully'));
setWindowClose(false);
} else if (result?.message_desc === 'Active appeal already exists for this file') {
toast.error(t('active-appeal-already-exists-for-this-file'));
}
else {
toast.error(result?.message_desc || t('appeal-submission-failed'));
}
} catch (error) {
console.error('Appeal submission error:', error);
toast.error(t('appeal-submission-failed'));
} finally {
setIsSubmitting(false);
}
};
return (
<div className="modal-window-view-file">
<div className="modal-window-view-file-content">
{/* Форма подачи апелляции */}
<div className="file-info-card appeal-form">
<div className="info-header">
<h4>{t('file-an-appeal')}</h4>
</div>
<div className="info-item">
<div className="info-label">
{t('appeal-reason')} <span className="required">*</span>
</div>
<div className="info-value">
<textarea
className="appeal-textarea"
value={appealReason}
onChange={(e) => setAppealReason(e.target.value)}
placeholder={t('enter-appeal-reason')}
rows={4}
disabled={isSubmitting}
/>
</div>
</div>
<div className="info-item">
<div className="info-label">{t('additional-info')}</div>
<div className="info-value">
<textarea
className="appeal-textarea"
value={additionalInfo}
onChange={(e) => setAdditionalInfo(e.target.value)}
placeholder={t('enter-additional-info')}
rows={3}
disabled={isSubmitting}
/>
</div>
<div className="appeal-actions">
<button
className="btn btn-cancel"
onClick={() => setWindowClose(false)}
disabled={isSubmitting}
>
{t('cancel')}
</button>
<button
className="btn btn-primary"
onClick={handleSubmitAppeal}
disabled={isSubmitting}
>
{isSubmitting ? t('submitting') : t('submit-appeal')}
</button>
</div>
</div>
</div>
</div>
</div>
);
}
@@ -16,7 +16,7 @@ import {
createColumnHelper,
RowSelectionState,
} from '@tanstack/react-table';
import { NotificationTextHandler } from '@/app/components/NotificationTextHandler';
export function NotificationsList() {
const t = useTranslations('Global');
@@ -81,6 +81,7 @@ export function NotificationsList() {
</>
)}
{allNotifications?.notifications.length !== 0 && (
<button
className="btn btn-primary"
onClick={() => {
@@ -103,6 +104,7 @@ export function NotificationsList() {
>
{allNotificationSelectedOnPage ? t('deselect') : t('select-all')}
</button>
)}
</div>
);
},
@@ -135,19 +137,23 @@ export function NotificationsList() {
}),
columnHelper.accessor('message', {
id: 'message',
header: () => null, // No header text, using custom header above
header: () => null,
cell: ({ row }) => {
const item = row.original;
return (
<div className="notification-body">
<div className="notification-header">
{/* Optional header content */}
</div>
<div className="notification-text">
<div
className="notification-title"
>
{norificationTranslate.has(item.message)
? norificationTranslate(item.message)
: item.message} ({item.id})
</div>
</div>
<div className="notification-text">
<NotificationTextHandler notificationType={item.type} notificationMessage={item.message} notificationContext={item.context} />
</div>
<div className="notification-footer">
<div className="notification-date">
{item.createdAt
@@ -429,9 +429,6 @@ export default function PaymentTabTransactionHistory() {
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length}
</span>
</div>
<div className="pagination-controls">
@@ -402,9 +402,6 @@ export default function InvitationsTable() {
<strong>
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
</span> */}
</div>
@@ -425,9 +425,6 @@ export default function PaymentsHistory() {
<strong>
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
</span> */}
</div>
+2 -2
View File
@@ -3,13 +3,13 @@
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
import { useTranslations } from 'next-intl';
import { useMonitoringStats } from '@/app/hooks/react-query/useMonitoringStats';
import { useViolationFinalSummaryStatistic } from '@/app/hooks/react-query/useViolationFinalSummaryStatistic';
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
export default function FinalSummary() {
const t = useTranslations('Global');
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
const { data: violationStatistic } = useViolationFinalSummaryStatistic();
const { data: monitoringStats } = useMonitoringStats();
const { data: violationStatistic } = useViolationStatistic();
return (
<div className="final-summary">
@@ -0,0 +1,462 @@
'use client'
import { useHistoryView, HistoryViewItem } from '@/app/hooks/react-query/useHistoryView';
import { useEffect, useState, useMemo } from 'react';
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons';
import DropDownList from '@/app/components/DropDownList';
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, SortingState, ColumnDef } from '@tanstack/react-table';
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import { useTranslations } from 'next-intl';
import { useViewport } from '@/app/hooks/useViewport';
import { useLocale } from 'next-intl';
export function TrackingHistoryView() {
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState<SortingState>([]);
const t = useTranslations('Global');
const locale = useLocale();
const getSortParams = useMemo(() => {
if (sorting.length === 0) {
return { sortBy: '', sortOrder: undefined };
}
const sort = sorting[0];
const sortByMap: Record<string, string> = {
'document': 'docName',
'view_date': 'createdAt',
'viewer': 'viewerName',
'location': locale === 'ru' ? 'geoNameRu' : 'geoNameEn'
};
return {
sortBy: sortByMap[sort.id] || sort.id,
sortOrder: sort.desc ? 'desc' : 'asc' as 'desc' | 'asc'
};
}, [sorting]);
const {
data: apiResponse,
isLoading,
isFetching,
isError,
error,
refetch
} = useHistoryView(pagination.pageIndex, pagination.pageSize, locale, getSortParams.sortOrder, getSortParams.sortBy);
const totalElements = apiResponse?.totalElements || 0
const tableData = apiResponse?.history || [];
const totalPages = apiResponse?.totalPages || 0;
const viewport = useViewport();
const getMaxLengthByWidth = (): number => {
if (viewport.device === 'MOBILE') return 22;
if (viewport.device === 'TABLET') return 30;
if (viewport.device === 'DESKTOP') return 50;
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 '...';
}
return nameWithoutExtension.substring(0, maxNameLength) + '...';
}
function EditLocationName({ location }: { location: string | undefined | null }) {
if (!location) return (
<div></div>
);
const [city, country] = location.split('/');
const translatedCity = city === 'unknow' ? t('not-defined-city') : city;
const translatedCountry = country === 'unknow' ? t('not-defined-country') : country;
return (
<div>
{t('city')}: <strong>{translatedCity}</strong>
<br />
{t('country')}: <strong>{translatedCountry}</strong>
</div>
);
}
const columns = useMemo<ColumnDef<HistoryViewItem>[]>(
() => [
{
accessorKey: 'document',
header: ({ column }) => (
<div className="column">
<span>
{t('document')}
</span>
<button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button"
>
{
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp />
</span>
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown />
</span>
: <span>
<IconFilter />
</span>
}
</button>
</div>
),
cell: ({ row }) => (
<div className="text-center table-item">
{row.original.document}
</div>
),
},
{
accessorKey: 'view_date',
header: ({ column }) => (
<div className="column">
<span>
{t('date')} / {t('time')}
</span>
<button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button"
>
{
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp />
</span>
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown />
</span>
: <span>
<IconFilter />
</span>
}
</button>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center table-item">
{row.original.view_date ? (
<>
{formatDate(row.original.view_date)}
<br />
{formatDateTime(row.original.view_date)}
<br />
</>
) : (
<div>-</div>
)}
</div>
)
},
enableColumnFilter: false,
},
{
accessorKey: 'viewer',
header: ({ column }) => (
<div className="column">
<span>
{t('who-opened')}
</span>
<button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button"
>
{
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp />
</span>
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown />
</span>
: <span>
<IconFilter />
</span>
}
</button>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center font-semibold table-item">
{row.original.viewer}
</div>
)
},
},
{
accessorKey: 'location',
header: ({ column }) => (
<div className="column column-location">
<span>
{t('location')}
</span>
<button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button"
>
{
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp />
</span>
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown />
</span>
: <span>
<IconFilter />
</span>
}
</button>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center font-semibold table-item">
{row.original.country === 'unknow/unknow' ? (
t('not-defined')
) : (
<EditLocationName location={row.original.country} />
)}
</div>
)
},
}
],
[]
);
/* `${editLocationName(row.original.country)}` */
// Создание таблицы
const table = useReactTable({
data: tableData,
columns,
state: {
sorting,
pagination
},
pageCount: totalPages,
manualPagination: true,
manualSorting: true,
manualFiltering: true,
onPaginationChange: setPagination,
onSortingChange: (updater) => {
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
setSorting(newSorting);
setPagination(prev => ({ ...prev, pageIndex: 0 }));
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
useEffect(() => {
if (tableData.length === 0 && pagination.pageIndex > 0 && totalPages > 0) {
setPagination(prev => ({ ...prev, pageIndex: Math.max(0, totalPages - 1) }));
}
}, [tableData, pagination.pageIndex, totalPages]);
return (
<div
className="block-wrapper"
>
<div className="tanstak-table-wrapper">
{/* Фильтры */}
<div className="tanstak-table-filtres">
<h3
className="tanstak-table-title"
>
{t('history-of-opened')}
</h3>
<div className="table-filtres-wrapper text-filter-search end">
<div className="table-filtres-item">
<div className="table-filtres-label">{t('items-per-page')}:</div>
<DropDownList
value={table.getState().pagination.pageSize}
callBack={(newSize: string) => {
table.setPageSize(Number(newSize));
setPagination(prev => ({ ...prev, pageIndex: 0 }));
}}
>
{['5', '10', '20', '50', '100'].map(pageSize => (
<li key={pageSize} value={pageSize}>
{t('show')} {pageSize}
</li>
))}
</DropDownList>
</div>
</div>
</div>
{/* Таблица */}
<div className="tanstak-table-block">
<table className="tanstak-table">
<thead className="tanstak-table-head">
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th
key={header.id}
>
{header.isPlaceholder
? null
: typeof header.column.columnDef.header === 'function'
? header.column.columnDef.header(header.getContext())
: header.column.columnDef.header as string}
</th>
))}
</tr>
))}
</thead>
<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.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{typeof cell.column.columnDef.cell === 'function'
? cell.column.columnDef.cell(cell.getContext())
: cell.getValue() as string}
</td>
))}
</tr>
))
) : (
<tr>
{!isLoading && (
<td colSpan={columns.length} className="text-center">
{t('no-data-for-selected-filters')}
</td>
)}
</tr>
)}
</tbody>
</table>
</div>
{/* Пагинация */}
<div className="tanstak-table-pagination">
<div className="pagination-info">
<span className="pagination-info-pages">
{t('page')}{' '}
<strong>
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
</strong> | {t('total-document-openings')}: {totalElements}
</span>
</div>
<div className="pagination-controls">
<button
className="arrow"
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage() || isLoading}
>
<IconDoubleArrowLeft />
</button>
<button
className="arrow"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage() || isLoading}
>
<IconArrowLeft />
</button>
<div className="pagination-controls-pages">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageIndex;
if (totalPages <= 5) {
pageIndex = i;
} else if (pagination.pageIndex <= 2) {
pageIndex = i;
} else if (pagination.pageIndex >= totalPages - 3) {
pageIndex = totalPages - 5 + i;
} else {
pageIndex = pagination.pageIndex - 2 + i;
}
if (pageIndex >= 0 && pageIndex < totalPages) {
return (
<button
key={pageIndex}
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
onClick={() => table.setPageIndex(pageIndex)}
disabled={isLoading}
>
{pageIndex + 1}
</button>
);
}
return null;
})}
</div>
<button
className="arrow"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage() || isLoading}
>
<IconArrowRight />
</button>
<button
className="arrow"
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage() || isLoading}
>
<IconDoubleArrowRight />
</button>
</div>
</div>
</div >
</div>
)
}
+79 -11
View File
@@ -1,5 +1,11 @@
import { IconLink, IconDocument, IconCheck, IconPerson, IconGlobe } from '@/app/ui/icons/icons';
'use client'
import { IconLink, IconDocument, IconCheck, IconPerson, IconGlobe, IconCity } from '@/app/ui/icons/icons';
import { useTrackingStats } from '@/app/hooks/react-query/useTrackingStats';
export function TrackingStatistic() {
const { data, isLoading } = useTrackingStats();
return (
<div
className="general-statistic"
@@ -21,7 +27,17 @@ export function TrackingStatistic() {
<IconDocument />
</div>
<div className="general-statistic-item-val">
33
<div
className="loading-placeholder start"
>
{isLoading ? (
<div className="loading-animation">
<div className="global-spinner" />
</div>
) : (
data?.documentsCount ? data?.documentsCount : 0
)}
</div>
</div>
<div className="general-statistic-item-lbl">
Документов
@@ -30,30 +46,51 @@ export function TrackingStatistic() {
<div className="general-statistic-item">
<div className="general-statistic-item-ico">
<IconLink />
</div><div className="general-statistic-item-val">
103
</div>
<div className="general-statistic-item-val">
<div
className="loading-placeholder start"
>
{isLoading ? (
<div className="loading-animation">
<div className="global-spinner" />
</div>
) : (
data?.totalViews ? data?.totalViews : 0
)}
</div>
</div>
<div className="general-statistic-item-lbl">
Открытий
</div>
</div>
<div className="general-statistic-item">
{/* <div className="general-statistic-item">
<div className="general-statistic-item-ico">
<IconCheck />
</div>
<div className="general-statistic-item-val">
247
{data?.uniqueUsers ? data?.uniqueUsers : 0}
</div>
<div className="general-statistic-item-lbl">
Верификаций
</div>
Уникальных пользователей
</div>
</div> */}
<div className="general-statistic-item">
<div className="general-statistic-item-ico">
<IconPerson />
</div>
<div className="general-statistic-item-val">
27
<div
className="loading-placeholder start"
>
{isLoading ? (
<div className="loading-animation">
<div className="global-spinner" />
</div>
) : (
data?.uniqueIps ? data?.uniqueIps : 0
)}
</div>
</div>
<div className="general-statistic-item-lbl">
Уникальных IP
@@ -64,10 +101,41 @@ export function TrackingStatistic() {
<IconGlobe />
</div>
<div className="general-statistic-item-val">
7
<div
className="loading-placeholder start"
>
{isLoading ? (
<div className="loading-animation">
<div className="global-spinner" />
</div>
) : (
data?.uniqueCountryCount ? data?.uniqueCountryCount : 0
)}
</div>
</div>
<div className="general-statistic-item-lbl">
Стран
Уникальных стран
</div>
</div>
<div className="general-statistic-item">
<div className="general-statistic-item-ico">
<IconCity />
</div>
<div className="general-statistic-item-val">
<div
className="loading-placeholder start"
>
{isLoading ? (
<div className="loading-animation">
<div className="global-spinner" />
</div>
) : (
data?.uniqueCountryCount ? data?.uniqueCountryCount : 0
)}
</div>
</div>
<div className="general-statistic-item-lbl">
Уникальных городов
</div>
</div>
</div>
@@ -1,6 +1,6 @@
'use client'
import { useState, useMemo, useCallback } from 'react';
import { useState, useMemo } from 'react';
import {
useReactTable,
getCoreRowModel,
@@ -51,7 +51,7 @@ export function ViolationsMyClaimsTable() {
isLoading,
isFetching,
} = useLastCases({
page: pagination.pageIndex + 1,
page: pagination.pageIndex,
size: pagination.pageSize,
sort_by: getSortParams.sort_by,
sort_direction: getSortParams.sort_direction ? getSortParams.sort_direction : 'desc',
@@ -270,10 +270,7 @@ export function ViolationsMyClaimsTable() {
{t('page')}{' '}
<strong>
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {tableData.length} {t('out-of')} {totalItems}
</strong> | {t('total-claims')}: {totalItems}
</span>
</div>
@@ -52,7 +52,7 @@ export function ViolationsMyComplaintsTable() {
isLoading,
isFetching,
} = useLastComplaints({
page: pagination.pageIndex + 1,
page: pagination.pageIndex,
size: pagination.pageSize,
sort_by: getSortParams.sort_by,
sort_direction: getSortParams.sort_direction ? getSortParams.sort_direction : 'desc',
@@ -91,7 +91,7 @@ export function ViolationsMyComplaintsTable() {
</div>
),
},
/* {
/* {
accessorKey: 'createdAt',
header: ({ column }) => (
<div className="column">
@@ -270,10 +270,7 @@ export function ViolationsMyComplaintsTable() {
{t('page')}{' '}
<strong>
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {tableData.length} {t('out-of')} {totalItems}
</strong> | {t('total-complaints')}: {totalItems}
</span>
</div>
@@ -125,9 +125,13 @@ export default function ViolationsLastCombinedCases() {
},
loop: filteredItems.length > 3,
pagination: {
clickable: true,
dynamicBullets: false,
},
/* pagination: {
el: '.custom-pagination-container', // Кастомный контейнер для пагинации
clickable: true,
},
}, */
onSwiper: (swiper: any) => {
swiperRef.current = swiper;
}
@@ -235,7 +239,7 @@ export default function ViolationsLastCombinedCases() {
)}
</div>
{!isNothingSelected && filteredItems.length > 0 && (
<div className="custom-pagination-container text-center"></div>
<div className="swiper-pagination-custom-wrapper"></div>
)}
</div>
);
+14 -21
View File
@@ -68,12 +68,6 @@ export default function ViolationsTable() {
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 {};
}
@@ -108,7 +102,6 @@ export default function ViolationsTable() {
end_date: dateRange.end_date,
}),
select: (data) => {
console.log(data);
return data;
},
placeholderData: (previousData) => previousData,
@@ -154,7 +147,7 @@ export default function ViolationsTable() {
return nameWithoutExtension.substring(0, maxNameLength) + '...';
};
const columns = useMemo<ColumnDef<FileViolation>[]>(
const baseColumns = useMemo<ColumnDef<FileViolation>[]>(
() => [
{
accessorKey: 'supportId',
@@ -283,8 +276,13 @@ export default function ViolationsTable() {
{row.original.countLawCase}
</div>
),
},
{
}
],
[t, viewport]
);
const actionsColumn = useMemo<ColumnDef<FileViolation>>(
() => ({
accessorKey: 'actions',
header: () => (
<div className="column">
@@ -295,7 +293,7 @@ export default function ViolationsTable() {
<div className="actions">
<div className="actions-group">
<Link
href={`/pages/file/${row.original.fileId}`}
href={`/pages/file/${row.original.fileId}?startDate=${dateRange.start_date || ''}&endDate=${dateRange.end_date || ''}`}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
@@ -304,11 +302,12 @@ export default function ViolationsTable() {
</div>
</div>
),
},
],
[t, viewport]
}),
[t, dateRange.start_date, dateRange.end_date]
);
const columns = useMemo(() => [...baseColumns, actionsColumn], [baseColumns, actionsColumn]);
const table = useReactTable({
data: violationData?.content || [],
columns,
@@ -366,8 +365,6 @@ export default function ViolationsTable() {
return t('for-a-week');
case 'month':
return t('for-a-month');
case 'older':
return t('older-than-a-month');
default:
return t('today');
}
@@ -378,7 +375,6 @@ export default function ViolationsTable() {
<li value="today">{t('today')}</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>
</div>
@@ -481,10 +477,7 @@ export default function ViolationsTable() {
{t('page')}{' '}
<strong>
{table.getState().pagination.pageIndex + 1} {t('out-of')} {violationData?.totalPages || 1}
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {violationData?.content?.length || 0} {t('out-of')} {violationData?.totalElements || 0}
</strong> | {t('total-matches')}: {violationData?.totalElements}
</span>
</div>
+51 -2
View File
@@ -385,7 +385,9 @@
"violation-id": "Violation ID",
"no-information-about-the-complaint": "No information about the complaint",
"the-complaint-has-not-been-registered": "The complaint has not been registered",
"the-claims-has-not-been-registered": "The claims has not been registered",
"the-complaint-has-been-registered": "The complaint has been registered",
"the-claims-has-been-registered": "The claims has been registered",
"all-statuses": "All statuses",
"all": "All",
"mark-all-as-read": "Mark all as read",
@@ -464,7 +466,34 @@
"please-enter-complaint-text-first": "Please enter complaint text first",
"please-enter-template-name": "Please enter template name",
"template-saved-successfully": "Template saved successfully",
"no-claims-complaints-found": "No complaints/claims found"
"no-claims-complaints-found": "No complaints/claims found",
"no-notifications": "No notifications",
"who-opened": "Who opened",
"time": "Time",
"location": "Location",
"history-of-opened": "History of opened",
"not-defined": "Not defined",
"not-defined-city": "Not defined",
"not-defined-country": "Not defined",
"total-document-openings": "Total document openings",
"total-matches": "Total matches",
"total-complaints": "Total complaints",
"total-claims": "Total claims",
"city": "City",
"closed": "Closed",
"blocked": "Blocked",
"pending-moderation": "Pending moderation",
"file-an-appeal": "File an appeal",
"please-enter-appeal-reason": "Please enter the reason for appeal",
"appeal-submitted-successfully": "Appeal submitted successfully",
"appeal-submission-failed": "Failed to submit appeal",
"appeal-reason": "Reason for appeal",
"enter-appeal-reason": "Describe the reason for your appeal...",
"additional-info": "Additional information",
"enter-additional-info": "Any additional information that may help...",
"submit-appeal": "Submit appeal",
"submitting": "Submitting...",
"active-appeal-already-exists-for-this-file": "Active appeal already exists for this file"
},
"Login-register-form": {
"and": "and",
@@ -595,7 +624,27 @@
"notification-token-not-found": "Token not found",
"notification-file-moderation": "File moderation event",
"notification-file-added": "File added to system",
"notification-search-operation-filed": "Search operation failed"
"notification-search-operation-filed": "Search operation failed",
"not-specified": "not specified",
"not-specified-date": "not specified",
"not-specified-file-name": "not specified",
"message-missing": "message missing",
"reason-not-specified": "reason not specified",
"no-comment": "no comment",
"unknown-user": "unknown user",
"error-not-described": "error not described",
"not-scheduled": "not scheduled",
"notification-without-text": "Notification without text",
"user-verified": "Status: {status}. Update date: {updateDate}",
"monitoring-result": "Monitoring result: {message}",
"complaint-status-changed": "Complaint status changed from {oldStatus} to {newStatus}. Violation: {violation}. File: {file}",
"file-added-to-system": "Status: {status}. File name: {fileName}",
"file-moderation-event": "File moderation for '{fileName}'. Status changed from {oldStatus} to {newStatus}. Comment: {comment}",
"payment-result": "Payment result: {status}. Amount: {amount}",
"referral-registered": "Referral registered: {referralUser}",
"search-result": "Search results from {startDate} to {endDate}. Type: {searchType}. Files found: {count}",
"search-operation-failed": "Status: {status}. Message: {message}. Reason: {reason}. Next run: {nextRun}",
"token-not-found": "Current: {currentTokens}, needed: {needTokens}. Message: {message}. Next run: {nextRun}"
},
"User-status": {
"under-moderation": "Under moderation",
+54 -7
View File
@@ -204,8 +204,8 @@
"document-occupy": "Занимают документы",
"violations-found": "Найдено нарушений",
"total-violations": "Всего нарушений",
"violations-registered": "Жалоб зарегистрированно",
"claims-registered": "Претензий зарегистрированно",
"violations-registered": "Жалоб зарегистрировано",
"claims-registered": "Претензий зарегистрировано",
"new": "Новые",
"in-progress": "В работе",
"it-decided": "Решено",
@@ -385,7 +385,9 @@
"violation-id": "ID нарушения",
"no-information-about-the-complaint": "Нет информации о жалобе",
"the-complaint-has-not-been-registered": "Жалоба не зарегистрирована",
"the-claims-has-not-been-registered": "Претензия не зарегистрирована",
"the-complaint-has-been-registered": "Жалоба зарегистрирована",
"the-claims-has-been-registered": "Претензия зарегистрирована",
"all-statuses": "Все статусы",
"all": "Все",
"mark-all-as-read": "Отметить все как прочитанные",
@@ -464,7 +466,34 @@
"please-enter-complaint-text-first": "Пожалуйста, сначала введите текст жалобы",
"please-enter-template-name": "Пожалуйста, введите название шаблона",
"template-saved-successfully": "Шаблон успешно сохранен",
"no-claims-complaints-found": "Жалобы/претензии не найдены"
"no-claims-complaints-found": "Жалобы / претензии не найдены",
"no-notifications": "Нет уведомлений",
"who-opened": "Кто открыл",
"time": "Время",
"location": "Местоположение",
"history-of-opened": "История открытий",
"not-defined": "Не определено",
"not-defined-city": "Не определен",
"not-defined-country": "Не определена",
"total-document-openings": "Всего открытий документов",
"total-matches": "Всего совпадений",
"total-complaints": "Всего нарушений",
"total-claims": "Всего претензий",
"city": "Город",
"closed": "Закрыта",
"blocked": "Заблокирован",
"pending-moderation": "На модерации",
"file-an-appeal": "Подать апелляцию",
"please-enter-appeal-reason": "Пожалуйста, укажите причину апелляции",
"appeal-submitted-successfully": "Апелляция успешно отправлена",
"appeal-submission-failed": "Не удалось отправить апелляцию",
"appeal-reason": "Причина апелляции",
"enter-appeal-reason": "Опишите причину вашей апелляции...",
"additional-info": "Дополнительная информация",
"enter-additional-info": "Любая дополнительная информация, которая может помочь...",
"submit-appeal": "Отправить апелляцию",
"submitting": "Отправка...",
"active-appeal-already-exists-for-this-file": "По данному делу уже подана активная апелляция"
},
"Login-register-form": {
"and": "и",
@@ -595,7 +624,27 @@
"notification-token-not-found": "Токен не найден",
"notification-file-moderation": "Модерация файла",
"notification-file-added": "Файл добавлен в систему",
"notification-search-operation-filed": "Ошибка операции поиска"
"notification-search-operation-filed": "Ошибка операции поиска",
"not-specified": "не указан",
"not-specified-date": "не указана",
"not-specified-file-name": "не указано",
"message-missing": "сообщение отсутствует",
"reason-not-specified": "причина не указана",
"no-comment": "без комментария",
"unknown-user": "неизвестный пользователь",
"error-not-described": "ошибка не описана",
"not-scheduled": "не запланирован",
"notification-without-text": "Уведомление без текста",
"user-verified": "Статус: {status}. Дата обновления: {updateDate}",
"monitoring-result": "Результат мониторинга: {message}",
"complaint-status-changed": "Статус жалобы изменён с {oldStatus} на {newStatus}. Нарушение: {violation}. Файл: {file}",
"file-added-to-system": "Статус: {status}. Имя файла: {fileName}",
"file-moderation-event": "Модерация файла '{fileName}'. Статус изменён с {oldStatus} на {newStatus}. Комментарий: {comment}",
"payment-result": "Результат платежа: {status}. Сумма: {amount}",
"referral-registered": "Зарегистрирован реферал: {referralUser}",
"search-result": "Результаты поиска с {startDate} по {endDate}. Тип: {searchType}. Найдено файлов: {count}",
"search-operation-failed": "Статус: {status}. Сообщение: {message}. Причина: {reason}. Следующий запуск: {nextRun}",
"token-not-found": "Текущие: {currentTokens}, необходимо: {needTokens}. Сообщение: {message}. Следующий запуск: {nextRun}"
},
"User-status": {
"under-moderation": "На модерации",
@@ -606,7 +655,5 @@
"user-verification": "Верификация пользователя",
"Status": "Статус"
},
"Countryes": {
}
"Countryes": {}
}
+1 -1
View File
@@ -37,7 +37,7 @@ export default async function proxy(request: NextRequest) {
)
const cookie = (await cookies()).get('session')?.value;
const cookie = (await cookies()).get('session_nc_db')?.value;
const session = await decrypt(cookie);
if (isProtectedRoute && !session?.token) {