From f65513b1cbc0e6f9315048a4e2ec92df8c862666 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 19 Mar 2026 16:15:18 +0700 Subject: [PATCH] update dashboard statisctic --- src/app/[locale]/pages/dashboard/page.tsx | 8 +- src/app/styles/pages-styles.scss | 80 ++++--- src/app/styles/payment-container.scss | 1 + .../ui/dashboard/new/dashboard-user-files.tsx | 5 +- .../ui/dashboard/new/dashboard-user-stats.tsx | 105 +++++---- .../new/dashboard-user-violations.tsx | 104 +++++++++ .../ui/dashboard/new/violations-carousel.tsx | 199 +++++------------- src/app/ui/payment/payment-tab-tariffs.tsx | 129 +++++++----- src/app/ui/violations/violations-table.tsx | 1 - src/i18n/messages/en.json | 9 +- src/i18n/messages/ru.json | 9 +- 11 files changed, 357 insertions(+), 293 deletions(-) create mode 100644 src/app/ui/dashboard/new/dashboard-user-violations.tsx diff --git a/src/app/[locale]/pages/dashboard/page.tsx b/src/app/[locale]/pages/dashboard/page.tsx index f8e6a92..ea2a835 100644 --- a/src/app/[locale]/pages/dashboard/page.tsx +++ b/src/app/[locale]/pages/dashboard/page.tsx @@ -11,6 +11,7 @@ import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-secti import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section'; import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check'; import ViolationsCarousel from '@/app/ui/dashboard/new/violations-carousel'; +import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations'; export const metadata: Metadata = { title: 'Dashboard', @@ -24,13 +25,16 @@ export default function Page() {
+ {/* + */} +
- {/* */}
- +
+
diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 1c55e8f..72effba 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -1539,45 +1539,20 @@ .stat-card { background: linear-gradient(135deg, var(--card-color-1), var(--card-color-2)); border-radius: 20px; - padding: 30px 24px; + padding: 32px 28px; color: white; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.4s ease; + cursor: pointer; position: relative; overflow: hidden; - &::before { - content: ''; - position: absolute; - top: -50%; - right: -50%; - width: 200%; - height: 200%; - background: radial-gradient(circle, rgba(255, 255, 255, 0.25) 0%, transparent 70%); - opacity: 0; - transition: opacity 0.4s ease; - } - &:hover { - opacity: 1; - transform: translateY(0px); - transition: 0.5s; - - &::before { - opacity: 1; - } + transform: translateY(-8px); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); } } - .stat-type { - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 1.2px; - opacity: 0.9; - margin-bottom: 16px; - } - .stat-value { font-size: 42px; font-weight: 800; @@ -1592,13 +1567,19 @@ opacity: 0.95; } + .stat-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + position: relative; + z-index: 1; + } + .stat-icon { - position: absolute; - bottom: 20px; - right: 20px; - font-size: 48px; - opacity: 0.2; - filter: blur(1px); + .icon { + transform: scale(1.5); + } } @media (max-width: 1360px) { @@ -1617,33 +1598,32 @@ .dashboard-main-grid { display: grid; - grid-template-columns: 1fr 1fr; - gap: 30px; + grid-template-columns: 50% 50%; margin-bottom: 40px; width: 100%; max-width: calc(100vw - var(--side-bar-width) - 60px); .left-column { - display: flex; flex-direction: column; - gap: 30px; width: 100%; - max-width: calc(100vw - var(--side-bar-width) - 40vw - 60px); + padding-right: 15px; } .right-column { width: 100%; - display: flex; flex-direction: column; - max-width: calc(100vw - var(--side-bar-width) - 40vw - 60px); + padding-left: 15px; } @media (max-width: 1360px) { grid-template-columns: 1fr; + gap: 30px; .left-column, .right-column { max-width: calc(100vw - var(--side-bar-width) - 60px); + padding-right: 0; + padding-left: 0; } } @@ -1714,6 +1694,18 @@ font-size: 24px; flex-shrink: 0; + &.violent { + a { + color: #fff; + cursor: pointer; + border-radius: 8px; + padding: 8px; + font-size: 14px; + line-height: 20px; + transition: all .3s ease-in; + } + } + &.video { background: linear-gradient(135deg, #f3e8ff, #e9d5ff); } @@ -2255,7 +2247,6 @@ text-transform: uppercase; background: #dbeafe; color: #1e40af; - /* display: none; */ } } @@ -2396,6 +2387,7 @@ grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; + max-width: calc(100vw - var(--side-bar-width) - 60px); @media (max-width: 1400px) { grid-template-columns: repeat(2, 1fr); diff --git a/src/app/styles/payment-container.scss b/src/app/styles/payment-container.scss index da239e0..7dcafec 100644 --- a/src/app/styles/payment-container.scss +++ b/src/app/styles/payment-container.scss @@ -249,6 +249,7 @@ .plan-period { color: #64748b; font-size: 0.85rem; + text-transform: lowercase; } .plan-tokens { diff --git a/src/app/ui/dashboard/new/dashboard-user-files.tsx b/src/app/ui/dashboard/new/dashboard-user-files.tsx index 6445c6e..2fa6f80 100644 --- a/src/app/ui/dashboard/new/dashboard-user-files.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-files.tsx @@ -8,7 +8,6 @@ import { convertBytes } from '@/app/lib/convertBytes'; import { useTranslations } from 'next-intl'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import Link from 'next/link'; -import { getSessionData } from '@/app/actions/session'; type FileItem = { id: string; @@ -86,7 +85,9 @@ export default function DashboardUserFiles() { return (
-

Ваши файлы

+

+ {t('your-files')} +

{ diff --git a/src/app/ui/dashboard/new/dashboard-user-stats.tsx b/src/app/ui/dashboard/new/dashboard-user-stats.tsx index e6640aa..976c7a0 100644 --- a/src/app/ui/dashboard/new/dashboard-user-stats.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-stats.tsx @@ -1,52 +1,83 @@ 'use client' import { useTranslations } from 'next-intl'; -import {convertBytes} from '@/app/lib/convertBytes'; -import {useUserFilesInfo} from '@/app/hooks/react-query/useUserFilesInfo'; +import { convertBytes } from '@/app/lib/convertBytes'; +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; +import { useEffect } from 'react'; +import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic'; +import {IconDocument, IconSearch, IconWarning, IconGraph, IconDiscet, IconShield} from '@/app/ui/icons/icons'; export default function DashboardUserStats() { const t = useTranslations("Global"); const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); + const { data: violationStatistic } = useViolationStatistic(); return ( -
+ <> +
-
-
ЗАЩИЩЁННЫЕ ФАЙЛЫ
-
{filesInfo?.total.protected ? filesInfo?.total.protected : 0}
-
Файлов под защитой
-
🛡️
-
- -
-
СОВПАДЕНИЯ
-
0
-
Найдено совпадений
-
🔍
-
- -
-
НАРУШЕНИЯ
-
{filesInfo?.total.violation ? filesInfo?.total.violation : 0}
-
Требуют внимания
-
⚠️
-
- -
-
ХРАНИЛИЩЕ
-
- {filesInfo?.total.size ? convertBytes(filesInfo?.total.size) : 0} +
+
+
+ +
+
+
{filesInfo?.total.protected ? filesInfo?.total.protected : 0}
+
+ {t('files-under-protection')} +
-
Использовано места
-
💾
-
-
-
ЖАЛОБЫ
-
0
-
Отправлено юристам
-
⚖️
+
+
+
+ +
+
+
{filesInfo?.total.check ? filesInfo?.total.check : 0}
+
+ {t('matches-found')} +
+
+ +
+
+
+ +
+
+
{violationStatistic?.total_violations ?? 0}
+
+ {t('violations')} +
+
+ +
+
+
+ +
+
+
+ {filesInfo?.total.size ? convertBytes(filesInfo?.total.size) : 0} +
+
+ {t('disk-space-used')} +
+
+ +
+
+
+ +
+
+
{violationStatistic?.in_progress_violations ?? 0}
+
+ {t('violations-in-progress')} +
+
-
+ ) } \ No newline at end of file diff --git a/src/app/ui/dashboard/new/dashboard-user-violations.tsx b/src/app/ui/dashboard/new/dashboard-user-violations.tsx new file mode 100644 index 0000000..8f4d811 --- /dev/null +++ b/src/app/ui/dashboard/new/dashboard-user-violations.tsx @@ -0,0 +1,104 @@ +'use client' + +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'; +import { getViolationFilesArray } from '@/app/actions/violationActions'; +import { IconEye } from '@/app/ui/icons/icons'; + +interface ViolationFile { + createdAt: string; + fileId: string; + fileName: string; + fileSize: number; + latestViolationDate: string; + mimeType: string; + status: string; + supportId: number; + violationCount: number; +} + +export default function DashboardUserViolations() { + const { + data: violationData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['violationData'], + queryFn: () => getViolationFilesArray(), + + select: (data) => { + return data?.slice(0, 5) || []; + }, + /* refetchInterval: 30000 */ + }); + + const t = useTranslations('Global'); + const formatDate = (timestamp: number | string) => { + const date = new Date(timestamp); + const day = date.getDate().toString().padStart(2, '0'); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const year = date.getFullYear(); + + return `${day}.${month}.${year}`; + }; + + return ( +
+
+

+ {t('violation')} +

+
+ + {t('add')} + +
+
+ +
+ + {violationData?.length ? ( + violationData?.map((file) => { + return ( +
+
+ + + +
+
+
+ {file.fileName} +
+
+ {file.createdAt ? formatDate(file.createdAt) : 0} • {t('violations-found')}: {file.violationCount ? file.violationCount : 0} +
+
+
+ ) + }) + ) : ( +
+ {t('there-are-no-files-yet')} +
+ )} +
+
+ ) +} \ No newline at end of file diff --git a/src/app/ui/dashboard/new/violations-carousel.tsx b/src/app/ui/dashboard/new/violations-carousel.tsx index c44bf7c..ce22e82 100644 --- a/src/app/ui/dashboard/new/violations-carousel.tsx +++ b/src/app/ui/dashboard/new/violations-carousel.tsx @@ -6,18 +6,50 @@ import Image from 'next/image'; import 'swiper/css'; import 'swiper/css/pagination'; import testImage from '@/app/src/image-preview.png' -import { useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; +import { useQuery } from '@tanstack/react-query'; +import { getViolationFilesArray } from '@/app/actions/violationActions'; + +interface ViolationFile { + createdAt: string; + fileId: string; + fileName: string; + fileSize: number; + latestViolationDate: string; + mimeType: string; + status: string; + supportId: number; + violationCount: number; +} export default function ViolationsCarousel() { + const { + data: violationData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['violationData'], + queryFn: () => getViolationFilesArray(), + + select: (data) => { + return data; + }, + /* refetchInterval: 30000 */ + }); const paginationRef = useRef(null); const t = useTranslations('Global'); + useEffect(() => { + console.log(violationData); + }, [violationData]) + return (
-

Нарушения

+

{t('violation')}

{t('are-no-violations')}
- {/*
+
- + {/* {violationData?.map(item => { + return ( + +
+ +
+
+ ) + })} */} +{/*
Preview @@ -113,151 +156,7 @@ export default function ViolationsCarousel() {
- - -
-
- Preview - -
НАРУШЕНИЕ
-
ОБРАБОТАНО
-
- -
-
-
- 🖼️banner111.png
-
- Фото -
-
- -
-
-
- 🌍 Регион -
-
- 🇺🇸 - США -
-
- -
-
📅 Обнаружено
-
- 17.10.2025
-
- -
-
🌐 Домен
-
- m.apkpure.com
-
- -
-
📊 Категория
-
- Единичное -
-
- -
-
📁 Тип
-
- Изображение
-
- -
-
⚡ Статус
-
- ✅ Обработано
-
-
- -
- - -
-
-
-
- -
-
- Preview - -
НАРУШЕНИЕ
-
ОБРАБОТАНО
-
- -
-
-
- 🖼️banner111.png
-
- Фото -
-
- -
-
-
- 🌍 Регион -
-
- 🇺🇸 - США -
-
- -
-
📅 Обнаружено
-
- 17.10.2025
-
- -
-
🌐 Домен
-
- m.apkpure.com
-
- -
-
📊 Категория
-
- Единичное -
-
- -
-
📁 Тип
-
- Изображение
-
- -
-
⚡ Статус
-
- ✅ Обработано
-
-
- -
- - -
-
-
-
+ */}
-
*/} +
) } \ No newline at end of file diff --git a/src/app/ui/payment/payment-tab-tariffs.tsx b/src/app/ui/payment/payment-tab-tariffs.tsx index 8d55047..a1e3383 100644 --- a/src/app/ui/payment/payment-tab-tariffs.tsx +++ b/src/app/ui/payment/payment-tab-tariffs.tsx @@ -53,6 +53,7 @@ export default function PaymentTabTariffs() { const [isProcessing, setIsProcessing] = useState(false); const [showWidget, setShowWidget] = useState(false); const [yooMoneyConfig, setYooMoneyConfig] = useState(null); + const [billingPer, setBillingPer] = useState<'per-month' | 'per-year'>('per-month'); const queryClient = useQueryClient(); async function createPayment(value: number, tariff: number) { @@ -183,6 +184,10 @@ export default function PaymentTabTariffs() { setShowWidget(false); } + function changeBilling(type: 'per-month' | 'per-year') { + setBillingPer(type) + } + return ( <> @@ -190,66 +195,80 @@ export default function PaymentTabTariffs() {
-
+
- - +
-
- {tariffData?.map((item: { - id: number; - name: string; - price: number; - tokens: number; - maxFilesCount: number; - diskSize: number; - }) => ( -
-
-

- {t.has(item.name) ? t(item.name) : item.name} -

-
{item.price ?? 0} ₽
-
в месяц
-
{item.tokens ?? 0} токенов включено
+ {billingPer === 'per-month' && ( +
+ {tariffData?.map((item: { + id: number; + name: string; + price: number; + tokens: number; + maxFilesCount: number; + diskSize: number; + }) => ( +
+
+

+ {t.has(item.name) ? t(item.name) : item.name} +

+
{item.price ?? 0} ₽
+
{t('per-month')}
+
{item.tokens ?? 0} токенов включено
+
+
    +
  • + + {item.maxFilesCount} файлов в месяц +
  • +
  • + + {item.diskSize ? convertBytes(item.diskSize) : 0} хранилища +
  • +
  • + + Базовые модули защиты +
  • +
  • + + Поддержка 24/7 +
  • +
  • + + API доступ +
  • +
+
+ +
-
    -
  • - - {item.maxFilesCount} файлов в месяц -
  • -
  • - - {item.diskSize ? convertBytes(item.diskSize) : 0} хранилища -
  • -
  • - - Базовые модули защиты -
  • -
  • - - Поддержка 24/7 -
  • -
  • - - API доступ -
  • -
-
- -
-
- ))} -
+ ))} +
+ )}
{/* Модальное окно с виджетом */} diff --git a/src/app/ui/violations/violations-table.tsx b/src/app/ui/violations/violations-table.tsx index 1787225..a45ece7 100644 --- a/src/app/ui/violations/violations-table.tsx +++ b/src/app/ui/violations/violations-table.tsx @@ -4,7 +4,6 @@ import { useMemo, useState } from 'react'; import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState } from '@tanstack/react-table'; import { useTranslations } from 'next-intl'; import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons'; -import DropDownList from '@/app/components/DropDownList'; import { useQuery } from '@tanstack/react-query'; import { getViolationFilesArray } from '@/app/actions/violationActions'; import { useViewport } from '@/app/hooks/useViewport'; diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 4775920..d4dc71a 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -342,7 +342,14 @@ "france": "France", "uk": "United Kingdom", "china": "China", - "distribution-analytics": "Distribution analytics" + "distribution-analytics": "Distribution analytics", + "annual": "Annual", + "per-month": "Per month", + "violation-count": "Violation count", + "your-files": "Your files", + "files-under-protection": "Files under protection", + "matches-found": "Matches found", + "violations-in-progress": "Violations in progress" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 0fdf93d..c582b4b 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -342,7 +342,14 @@ "france": "Франция", "uk": "Великобритания", "china": "Китай", - "distribution-analytics": "Аналитика распространения" + "distribution-analytics": "Аналитика распространения", + "annual": "Годовая", + "per-month": "Помесячно", + "violation-count": "Количество нарушений", + "your-files": "Ваши файлы", + "files-under-protection": "Файлов под защитой", + "matches-found": "Найдено совпадений", + "violations-in-progress": "Нарушений в работе" }, "Login-register-form": { "and": "и",