From 7aacea387eb47ab178abfed243d2bb4ee94978f0 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 12 Dec 2025 20:28:59 +0700 Subject: [PATCH 01/17] add tanstak table --- src/app/components/dropDownList.tsx | 79 ++++ src/app/components/tanstakTable.tsx | 606 ++++++++++++++++++--------- src/app/styles/dashboard.scss | 7 - src/app/ui/dashboard/files-table.tsx | 6 +- src/app/ui/dashboard/stats-grid.tsx | 2 +- src/app/ui/icons/icons.tsx | 51 +++ src/i18n/messages/en.json | 20 +- src/i18n/messages/ru.json | 20 +- 8 files changed, 585 insertions(+), 206 deletions(-) create mode 100644 src/app/components/dropDownList.tsx diff --git a/src/app/components/dropDownList.tsx b/src/app/components/dropDownList.tsx new file mode 100644 index 0000000..8a3a9e5 --- /dev/null +++ b/src/app/components/dropDownList.tsx @@ -0,0 +1,79 @@ +import React, { ReactNode, useState, useRef } from 'react'; +import { useClickOutside } from '@/app/hooks/useClickOutside'; + +interface DropDownListProps { + children: ReactNode; + value: string | number; + callBack: (value: string) => void; +} + +interface ChildProps { + value?: string; + onClick?: () => void; + className?: string; + children?: React.ReactNode; +} + +export default function DropDownList({ children, value, callBack }: DropDownListProps) { + const [isOpen, setIsOpen] = useState(false) + const dropdownRef = useRef(null) + useClickOutside(dropdownRef, () => { + setIsOpen(false); + }); + + const enhancedChildren = React.Children.map(children, (child, index) => { + if (React.isValidElement(child)) { + const childValue = child.props.value || undefined; + + return React.cloneElement(child, { + onClick: () => { + callBack(childValue || ""); + setIsOpen(false); + }, + className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 text-[#6366f1]`, + key: index + }); + } + return child; + }); + + return ( +
+ + {isOpen && ( +
+
    + {enhancedChildren} +
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 70f265a..7b6d976 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -10,195 +10,400 @@ import { ColumnDef, SortingState, ColumnFiltersState, - FilterFn, } from '@tanstack/react-table'; -import { rankItem } from '@tanstack/match-sorter-utils'; +import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDownload, IconShield, DoubleArrowRight, ArrowRight, DoubleArrowLeft, ArrowLeft, ArrowUp, ArrowDown, Filter } from '@/app/ui/icons/icons'; +import { useTranslations } from 'next-intl'; +import DropDownList from '@/app/components/dropDownList'; // Типы данных -type Person = { +type FileType = 'image' | 'video' | 'audio'; +type FileItem = { id: number; - name: string; - age: number; - email: string; - department: string; - salary: number; + fileName: string; + fileType: FileType; + violations: number; + checks: number; + lastCheck: number; // timestamp в миллисекундах }; -// Кастомная функция фильтрации -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - const itemRank = rankItem(row.getValue(columnId), value); - addMeta({ itemRank }); - return itemRank.passed; -}; - -// Определяем тип для фильтров -declare module '@tanstack/react-table' { - interface FilterFns { - fuzzy: FilterFn; +// Иконки для типов файлов +const FileTypeIcon = ({ type }: { type: FileType }) => { + switch (type) { + case 'image': + return + + ; + case 'video': + return + + ; + case 'audio': + return + + ; + default: + return 📄; } -} +}; -export default function DataTable() { - // Данные - const [data] = useState([ - { id: 1, name: 'Иван Петров', age: 28, email: 'ivan@example.com', department: 'IT', salary: 80000 }, - { id: 2, name: 'Анна Сидорова', age: 32, email: 'anna@example.com', department: 'HR', salary: 65000 }, - { id: 3, name: 'Сергей Иванов', age: 45, email: 'sergey@example.com', department: 'Финансы', salary: 95000 }, - { id: 4, name: 'Мария Кузнецова', age: 26, email: 'maria@example.com', department: 'Маркетинг', salary: 60000 }, - { id: 5, name: 'Алексей Смирнов', age: 38, email: 'alex@example.com', department: 'IT', salary: 85000 }, - { id: 6, name: 'Ольга Васнецова', age: 29, email: 'olga@example.com', department: 'HR', salary: 62000 }, - { id: 7, name: 'Дмитрий Орлов', age: 41, email: 'dmitry@example.com', department: 'Финансы', salary: 92000 }, - { id: 8, name: 'Екатерина Новикова', age: 35, email: 'ekaterina@example.com', department: 'Маркетинг', salary: 70000 }, +// Форматирование даты из timestamp +const formatDate = (timestamp: number) => { + 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}`; +}; + +// Компонент таблицы +export default function TanstakFilesTable() { + // Данные с timestamp вместо Date объектов + const [data] = useState([ + { id: 1, fileName: 'image1.jpg', fileType: 'image', violations: 5, checks: 12, lastCheck: 1705267200000 }, // 15.01.2024 + { id: 2, fileName: 'presentation.mp4', fileType: 'video', violations: 2, checks: 8, lastCheck: 1705180800000 }, // 14.01.2024 + { id: 3, fileName: 'song.mp3', fileType: 'audio', violations: 0, checks: 5, lastCheck: 1705094400000 }, // 13.01.2024 + { id: 4, fileName: 'photo1.jpg', fileType: 'image', violations: 3, checks: 10, lastCheck: 1705008000000 }, // 12.01.2024 + { id: 5, fileName: 'movie.mp4', fileType: 'video', violations: 1, checks: 7, lastCheck: 1704921600000 }, // 11.01.2024 + { id: 6, fileName: 'podcast.mp3', fileType: 'audio', violations: 4, checks: 9, lastCheck: 1704835200000 }, // 10.01.2024 + { id: 7, fileName: 'screenshot.png', fileType: 'image', violations: 0, checks: 3, lastCheck: 1704748800000 }, // 09.01.2024 + { id: 8, fileName: 'tutorial.mp4', fileType: 'video', violations: 2, checks: 6, lastCheck: 1704662400000 }, // 08.01.2024 + { id: 9, fileName: 'music.mp3', fileType: 'audio', violations: 1, checks: 4, lastCheck: 1704576000000 }, // 07.01.2024 + { id: 10, fileName: 'image2.jpg', fileType: 'image', violations: 5, checks: 11, lastCheck: 1704489600000 }, // 06.01.2024 + { id: 11, fileName: 'film.mov', fileType: 'video', violations: 0, checks: 2, lastCheck: 1704403200000 }, // 05.01.2024 + { id: 12, fileName: 'sound.wav', fileType: 'audio', violations: 3, checks: 8, lastCheck: 1704316800000 }, // 04.01.2024 + { id: 13, fileName: 'picture.png', fileType: 'image', violations: 2, checks: 7, lastCheck: 1704230400000 }, // 03.01.2024 + { id: 14, fileName: 'video2.mp4', fileType: 'video', violations: 1, checks: 5, lastCheck: 1704144000000 }, // 02.01.2024 + { id: 15, fileName: 'audio2.mp3', fileType: 'audio', violations: 0, checks: 3, lastCheck: 1704057600000 }, // 01.01.2024 ]); // Состояния const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); - const [globalFilter, setGlobalFilter] = useState(''); - const [rowSelection, setRowSelection] = useState({}); + const [dateFilter, setDateFilter] = useState('all'); + const [typeFilter, setTypeFilter] = useState('all'); + + const t = useTranslations("Global"); // Определение колонок - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { - id: 'select', - header: ({ table }) => ( - + accessorKey: 'fileName', + header: ({ column }) => ( +
+ + {t('file')} + + +
), cell: ({ row }) => ( - +
+ +
+
{row.original.fileName}
+
+
), - enableSorting: false, enableColumnFilter: false, }, { - accessorKey: 'id', - header: 'ID', - cell: (info) => info.getValue(), + accessorKey: 'violations', + header: ({ column }) => ( +
+ + {t('violations')} + + +
+ ), + cell: ({ row }) => ( +
0 ? 'text-red-600' : 'text-green-600'}`}> + {row.original.violations} +
+ ), }, { - accessorKey: 'name', - header: 'Имя', - cell: (info) => info.getValue(), - filterFn: fuzzyFilter, + accessorKey: 'checks', + header: ({ column }) => ( +
+ + {t('checks')} + + +
+ ), + cell: ({ row }) => ( +
+ {row.original.checks} +
+ ), }, { - accessorKey: 'age', - header: 'Возраст', - cell: (info) => info.getValue(), + accessorKey: 'lastCheck', + header: ({ column }) => ( +
+ + {t('last-check')} + + +
+ ), + cell: ({ row }) => ( +
+ {formatDate(row.original.lastCheck)} +
+ ), + enableColumnFilter: false, }, { - accessorKey: 'email', - header: 'Email', - cell: (info) => info.getValue(), - }, - { - accessorKey: 'department', - header: 'Отдел', - cell: (info) => info.getValue(), - }, - { - accessorKey: 'salary', - header: 'Зарплата', - cell: (info) => `$${Number(info.getValue()).toLocaleString()}`, + id: 'actions', + header: () => { + return ( +
+ {t('actions')} +
+ ) + }, + cell: ({ row }) => ( +
+ + + +
+ ), + enableSorting: false, + enableColumnFilter: false, }, ], [] ); + // Обработчики действий + const handleView = (file: FileItem) => { + console.log(`Просмотр файла: ${file.fileName}`); + }; + + const handleDownload = (file: FileItem) => { + console.log(`Скачать: ${file.fileName}`); + }; + + const handleProtect = (file: FileItem) => { + console.log(`Щиток: ${file.fileName}`); + }; + + // Фильтрация по типу файла и дате + const filteredData = useMemo(() => { + let result = data; + + // Фильтр по типу файла + if (typeFilter !== 'all') { + result = result.filter(item => item.fileType === typeFilter); + } + + // Фильтр по дате + if (dateFilter !== 'all') { + const now = Date.now(); + const oneDay = 24 * 60 * 60 * 1000; + const sevenDays = 7 * oneDay; + const thirtyDays = 30 * oneDay; + + switch (dateFilter) { + case 'today': + const todayStart = new Date().setHours(0, 0, 0, 0); + result = result.filter(item => item.lastCheck >= todayStart); + break; + case 'week': + const weekAgo = now - sevenDays; + result = result.filter(item => item.lastCheck >= weekAgo); + break; + case 'month': + const monthAgo = now - thirtyDays; + result = result.filter(item => item.lastCheck >= monthAgo); + break; + case 'older': + const monthAgo2 = now - thirtyDays; + result = result.filter(item => item.lastCheck < monthAgo2); + break; + } + } + + return result; + }, [data, typeFilter, dateFilter]); + // Создание таблицы const table = useReactTable({ - data, + data: filteredData, columns, - filterFns: { - fuzzy: fuzzyFilter, - }, state: { sorting, columnFilters, - globalFilter, - rowSelection, }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, - onGlobalFilterChange: setGlobalFilter, - onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), getFilteredRowModel: getFilteredRowModel(), - globalFilterFn: fuzzyFilter, // Используем саму функцию, а не строку + initialState: { + pagination: { + pageSize: 10, + }, + }, }); return ( -
-

Таблица сотрудников

+
+ {/* Фильтры */} +
+
+
+
{t('date-filter')}:
+ +
  • + {t('all-dates')} +
  • +
  • + {t('today')} +
  • +
  • + {t('for-a-week')} +
  • +
  • + {t('for-a-month')} +
  • +
  • + {t('older-than-a-month')} +
  • +
    +
    - {/* Глобальный поиск */} -
    - setGlobalFilter(e.target.value)} - className="px-4 py-2 border rounded-md w-full max-w-md" - /> -
    +
    +
    {t('type-filter')}:
    + +
  • + {t('all-types')} +
  • +
  • + {t('images')} +
  • +
  • + {t('videos')} +
  • +
  • + {t('audios')} +
  • +
    +
    +
    - {/* Фильтры по колонкам */} -
    - {table.getAllColumns() - .filter(column => column.getCanFilter() && column.id !== 'select') - .map(column => ( -
    - - {column.id === 'department' ? ( - - ) : column.id === 'age' ? ( - column.setFilterValue(e.target.value ? parseInt(e.target.value) : undefined)} - className="px-3 py-1 border rounded w-32" - /> - ) : ( - column.setFilterValue(e.target.value)} - className="px-3 py-1 border rounded" - /> - )} -
    - ))} +
    +
    {t('items-per-page')}:
    + + {[5, 10, 20, 50, 100].map(pageSize => ( +
  • + {t('show')} {pageSize} +
  • + ))} +
    +
    {/* Таблица */} -
    +
    {table.getHeaderGroups().map(headerGroup => ( @@ -206,96 +411,115 @@ export default function DataTable() { {headerGroup.headers.map(header => ( ))} ))} - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + )) + ) : ( + + - ))} + )}
    -
    - {header.isPlaceholder - ? null - : typeof header.column.columnDef.header === 'string' - ? header.column.columnDef.header - : header.column.id} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
    + {header.isPlaceholder + ? null + : typeof header.column.columnDef.header === 'function' + ? header.column.columnDef.header(header.getContext()) + : header.column.columnDef.header as string}
    - {typeof cell.column.columnDef.cell === 'function' - ? cell.column.columnDef.cell(cell.getContext()) - : cell.getValue() as string} -
    + {typeof cell.column.columnDef.cell === 'function' + ? cell.column.columnDef.cell(cell.getContext()) + : cell.getValue() as string} +
    + {t('no-data-for-selected-filters')} +
    {/* Пагинация */} -
    +
    +
    + + {t('page')}{' '} + + {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount()} + + + + | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {t('files')} + +
    +
    + +
    + {Array.from({ length: Math.min(5, table.getPageCount()) }, (_, i) => { + const pageIndex = Math.max( + 0, + Math.min( + table.getPageCount() - 5, + table.getState().pagination.pageIndex - 2 + ) + ) + i; + + if (pageIndex < table.getPageCount()) { + return ( + + ); + } + return null; + })} +
    +
    - -
    - - Страница{' '} - - {table.getState().pagination.pageIndex + 1} из {table.getPageCount()} - - - - - Всего строк: {data.length} | Отфильтровано: {table.getFilteredRowModel().rows.length} - -
    ); diff --git a/src/app/styles/dashboard.scss b/src/app/styles/dashboard.scss index 369a144..9507ebd 100644 --- a/src/app/styles/dashboard.scss +++ b/src/app/styles/dashboard.scss @@ -128,13 +128,6 @@ } .stats-wrapper { - border-radius: 20px; - overflow: hidden; - background: white; - box-shadow: 0 4px 20px #00000014; - margin-bottom: 30px; - padding: 20px; - h3 { font-size: 20px; margin-bottom: 10px; diff --git a/src/app/ui/dashboard/files-table.tsx b/src/app/ui/dashboard/files-table.tsx index 6c96b70..e0b6de3 100644 --- a/src/app/ui/dashboard/files-table.tsx +++ b/src/app/ui/dashboard/files-table.tsx @@ -1,8 +1,8 @@ -import DataTable from '@/app/components/tanstakTable'; +import TanstakFilesTable from '@/app/components/tanstakTable'; export default function FilesTable() { return ( -
    - +
    +
    ) } \ No newline at end of file diff --git a/src/app/ui/dashboard/stats-grid.tsx b/src/app/ui/dashboard/stats-grid.tsx index 13ab89f..e6514ea 100644 --- a/src/app/ui/dashboard/stats-grid.tsx +++ b/src/app/ui/dashboard/stats-grid.tsx @@ -4,7 +4,7 @@ export default function StatsGrid() { const t = useTranslations("Global"); return ( -
    +

    {t('your-content')}

    diff --git a/src/app/ui/icons/icons.tsx b/src/app/ui/icons/icons.tsx index d9b324d..dd5572d 100644 --- a/src/app/ui/icons/icons.tsx +++ b/src/app/ui/icons/icons.tsx @@ -36,4 +36,55 @@ export function IconDownload() { ) +} + +export function IconEye() { + return ( + + + + ) +} + +export function DoubleArrowRight() { + return ( + + ) +} + +export function DoubleArrowLeft() { + return ( + + ) +} + +export function ArrowRight() { + return ( + + + ) +} + +export function ArrowLeft() { + return ( + + ) +} + +export function ArrowUp() { + return ( + + ) +} + +export function ArrowDown() { + return ( + + ) +} + +export function Filter() { + return ( + + ) } \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 721a1f8..bfddbfb 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -4,9 +4,12 @@ "description": "This is a demo application with i18n support" }, "Global": { + "actions": "Actions", + "all-types": "All types", "statistics": "Statistics", "your-content": "Your content", - "fail": "Fail", + "file": "File", + "files": "Files", "content-type": "Content type", "quantity": "Quantity", "size": "Size", @@ -37,7 +40,20 @@ "current-status-of": "Current status of security and activity of the monitoring system", "total-files": "Total files", "disk-space-used": "Disk space used", - "out-of": "out of" + "out-of": "out of", + "last-check": "Last check", + "all-dates": "All dates", + "today": "Today", + "for-a-week": "For a week", + "for-a-month": "For a month", + "older-than-a-month": "Older than a month", + "show": "Show", + "no-data-for-selected-filters": "No data for selected filters", + "page": "Page", + "shown": "Shown", + "date-filter": "Date filter", + "type-filter": "Type filter", + "items-per-page": "Items per page" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 5ac29b0..e4adcc1 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -4,9 +4,12 @@ "description": "Это демонстрационное приложение с поддержкой i18n" }, "Global": { + "actions": "Действия", + "all-types": "Все типы", "statistics": "Статистика", "your-content": "Ваш контент", - "fail": "Фаил", + "file": "Фаил", + "files": "Фаил", "content-type": "Тип контента", "quantity": "Количество", "size": "Размер", @@ -37,7 +40,20 @@ "current-status-of": "Текущий статус защищенности и активности системы мониторинга", "total-files": "Всего файлов", "disk-space-used": "Использовано места на диске", - "out-of": "из" + "out-of": "из", + "last-check": "Последняя проверка", + "all-dates": "Все даты", + "today": "Сегодня", + "for-a-week": "За неделю", + "for-a-month": "За месяц", + "older-than-a-month": "Старше месяца", + "show": "Показать", + "no-data-for-selected-filters": "Нет данных по выбранным фильтрам", + "page": "Страница", + "shown": "Показано", + "date-filter": "Фильтр по дате", + "type-filter": "Фильтр по типу", + "items-per-page": "Записей на странице" }, "Login-register-form": { "and": "и", From 3334059df7beac471641d18622201e99726b79f9 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 15 Dec 2025 10:34:20 +0700 Subject: [PATCH 02/17] remove border radius from navigation --- src/app/styles/ui.module.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/styles/ui.module.scss b/src/app/styles/ui.module.scss index 1bab389..f25dc83 100644 --- a/src/app/styles/ui.module.scss +++ b/src/app/styles/ui.module.scss @@ -2,7 +2,6 @@ width: 280px; background: linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%); padding: 30px 0; - border-radius: 0 30px 30px 0; position: fixed; height: 100vh; overflow-y: auto; From 72bd21c648be9bcb130ee0c2a8c5ace8f05ef183 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 15 Dec 2025 11:24:07 +0700 Subject: [PATCH 03/17] change stats-table style --- src/app/components/tanstakTable.tsx | 30 ++++++++++++++++++++++++++--- src/app/styles/dashboard.scss | 6 ++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 7b6d976..a49ab96 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -343,7 +343,20 @@ export default function TanstakFilesTable() {
    {t('date-filter')}:
    { + switch (dateFilter) { + case 'all': + return t('all-dates') + case 'week': + return t('for-a-week') + case 'month': + return t('for-a-month') + case 'older': + return t('older-than-a-month') + default: + return t('today') + } + })()} callBack={setDateFilter} >
  • @@ -364,10 +377,21 @@ export default function TanstakFilesTable() {
  • -
    +
    {t('type-filter')}:
    { + switch (typeFilter) { + case 'image': + return t('images') + case 'video': + return t('videos') + case 'audio': + return t('audios') + default: + return t('all-types') + } + })()} callBack={setTypeFilter} >
  • diff --git a/src/app/styles/dashboard.scss b/src/app/styles/dashboard.scss index 9507ebd..9157cc6 100644 --- a/src/app/styles/dashboard.scss +++ b/src/app/styles/dashboard.scss @@ -165,13 +165,15 @@ &.stats-header { background: #f9fafb; border-left: none; - font-size: 18px; + font-size: 16px; + font-weight: 500; } &.first-column { - font-size: 18px; + font-size: 16px; justify-content: start; padding-left: 20px; + font-weight: 500; .icon { color: var(--card-color-1); From 7fb6f7418f091cd068e5cfa68a02d84b41a15279 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 15 Dec 2025 12:22:42 +0700 Subject: [PATCH 04/17] add show password button --- src/app/components/tanstakTable.tsx | 4 +-- src/app/styles/dashboard.scss | 3 -- src/app/styles/global-styles.scss | 19 +++++++++++++ src/app/styles/login.module.scss | 4 +++ src/app/ui/register-form.tsx | 44 +++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index a49ab96..7afe0db 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -340,7 +340,7 @@ export default function TanstakFilesTable() { {/* Фильтры */}
    -
    +
    {t('date-filter')}:
    { @@ -377,7 +377,7 @@ export default function TanstakFilesTable() {
    -
    +
    {t('type-filter')}:
    { diff --git a/src/app/styles/dashboard.scss b/src/app/styles/dashboard.scss index 9157cc6..056c1c4 100644 --- a/src/app/styles/dashboard.scss +++ b/src/app/styles/dashboard.scss @@ -181,9 +181,6 @@ } } - &.last-column { - } - &.last-row { border-bottom: 1px solid var(--boder-collor); } diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 4bd1313..bc96ce4 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -92,4 +92,23 @@ margin: 0 auto; padding: 0 24px; } +} + +.show-password-button { + cursor: pointer; + position: absolute; + top: 0px; + right: 7px; + + svg { + width: 16px; + color: #686060; + transition: color 0.2s ease; + } + + &.show { + svg { + color: black; + } + } } \ No newline at end of file diff --git a/src/app/styles/login.module.scss b/src/app/styles/login.module.scss index e3f652f..e8c24d2 100644 --- a/src/app/styles/login.module.scss +++ b/src/app/styles/login.module.scss @@ -188,4 +188,8 @@ font-size: 13px; line-height: 1.4; } +} + +.password-wrapper { + position: relative; } \ No newline at end of file diff --git a/src/app/ui/register-form.tsx b/src/app/ui/register-form.tsx index 06778b2..48885b8 100644 --- a/src/app/ui/register-form.tsx +++ b/src/app/ui/register-form.tsx @@ -1,11 +1,12 @@ 'use client' import styles from '@/app/styles/login.module.scss'; -import { useActionState } from 'react'; +import { useActionState, useState } from 'react'; import { registration } from '@/app/actions/auth'; import PhoneInput from '@/app/ui/inputs/phone-input'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; +import { IconEye } from '@/app/ui/icons/icons'; export default function RegisterForm() { const [state, formAction, isPending] = useActionState( @@ -14,6 +15,20 @@ export default function RegisterForm() { ); const t = useTranslations('Login-register-form'); + const [showPassword, setShowPassword] = useState(false); + + function handleMouseDown() { + setShowPassword(true); + } + + function handleMouseUp() { + setShowPassword(false); + } + + function handleMouseOut() { + setShowPassword(false); + } + return (
    { formAction(e); @@ -89,13 +104,26 @@ export default function RegisterForm() { - +
    + + +
    {state?.error?.password && (

    { From b0d1b954ddc559950dbd0860e3fd43aaaafe1ed1 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 15 Dec 2025 12:42:50 +0700 Subject: [PATCH 05/17] add show-password-button for login form --- src/app/styles/global-styles.scss | 2 +- src/app/ui/login-form.tsx | 42 +++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index bc96ce4..a967f4b 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -98,7 +98,7 @@ cursor: pointer; position: absolute; top: 0px; - right: 7px; + right: 6px; svg { width: 16px; diff --git a/src/app/ui/login-form.tsx b/src/app/ui/login-form.tsx index 896d600..b6ca382 100644 --- a/src/app/ui/login-form.tsx +++ b/src/app/ui/login-form.tsx @@ -1,9 +1,10 @@ 'use client' import styles from '@/app/styles/login.module.scss' -import { useActionState } from 'react'; +import { useActionState, useState } from 'react'; import { authorization } from '@/app/actions/auth'; import { useTranslations } from 'next-intl'; +import { IconEye } from '@/app/ui/icons/icons'; export default function LoginForm() { const [state, formAction, isPending] = useActionState( @@ -12,6 +13,20 @@ export default function LoginForm() { ); const t = useTranslations('Login-register-form'); + const [showPassword, setShowPassword] = useState(false); + + function handleMouseDown() { + setShowPassword(true); + } + + function handleMouseUp() { + setShowPassword(false); + } + + function handleMouseOut() { + setShowPassword(false); + } + return (

    @@ -36,13 +51,24 @@ export default function LoginForm() { - +
    + + +
    {state?.error?.password && (

    {t(state?.error?.password)} From 79df200ccbcdff46a3cb325bb9b031d97d8fddf4 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 15 Dec 2025 17:32:30 +0700 Subject: [PATCH 06/17] edit styles, add vk-button and initial setup for vk auth --- package-lock.json | 20 ++++++++- package.json | 3 +- src/app/[locale]/login/page.tsx | 4 ++ src/app/components/VKLogin.tsx | 48 +++++++++++++++++++++ src/app/components/dropDownList.tsx | 2 +- src/app/styles/VKLogin.scss | 52 +++++++++++++++++++++++ src/app/styles/global-styles.scss | 1 + src/app/styles/marking-pages.scss | 2 +- src/app/ui/header/notificationsButton.tsx | 2 +- 9 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/app/components/VKLogin.tsx create mode 100644 src/app/styles/VKLogin.scss diff --git a/package-lock.json b/package-lock.json index fac1abe..60c4472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "no-copy-frontend", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "no-copy-frontend", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@tailwindcss/postcss": "^4.1.17", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", "@tanstack/react-table": "^8.21.3", + "@vkid/sdk": "^2.6.2", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", @@ -2953,6 +2954,15 @@ "win32" ] }, + "node_modules/@vkid/sdk": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@vkid/sdk/-/sdk-2.6.2.tgz", + "integrity": "sha512-TCaGh6BvQNY8Atru0KJXdGuwm70y9WS2xBOLNripHjYMEcft0BMIiLKUvJ7pAeJOtk/15MvPaUIEmBS2Vt0NBg==", + "license": "MIT", + "dependencies": { + "crypto-js": "^4.1.1" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3491,6 +3501,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", diff --git a/package.json b/package.json index aefcc87..e7a05ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.1.0", + "version": "0.2.0", "private": true, "scripts": { "dev": "next dev -p 2999", @@ -13,6 +13,7 @@ "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", "@tanstack/react-table": "^8.21.3", + "@vkid/sdk": "^2.6.2", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx index 22194bf..0848637 100644 --- a/src/app/[locale]/login/page.tsx +++ b/src/app/[locale]/login/page.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import LoginForm from '@/app/ui/login-form'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import { useTranslations } from 'next-intl'; +import VKLogin from '@/app/components/VKLogin'; export const metadata: Metadata = { title: 'Login', @@ -32,6 +33,9 @@ export default function Page() { {t('or-log-in-via')}

    +
    + +

    {t('no-account')}? {t('register')} diff --git a/src/app/components/VKLogin.tsx b/src/app/components/VKLogin.tsx new file mode 100644 index 0000000..9bd07ca --- /dev/null +++ b/src/app/components/VKLogin.tsx @@ -0,0 +1,48 @@ +'use client' + +import { useEffect, useRef } from 'react'; +import * as VKID from '@vkid/sdk'; + +export default function VKLogin() { + + const vkButton = useRef(null) + + function clickHandler() { + alert('Кнопка пока не подключена к бизнес аккаунту!'); +/* VKID.Auth.login() + .then(vkidOnSuccess) + .catch(console.error); */ + } + +/* function vkidOnSuccess(data: any) { + console.log('vkidOnSuccess'); + console.log(data); + } */ + +/* useEffect(() => { + VKID.Config.init({ + app: 54389559, + redirectUrl: 'http://localhost', + responseMode: VKID.ConfigResponseMode.Callback, + source: VKID.ConfigSource.LOWCODE, + scope: 'email' + }); + }, []); */ + + return ( + + ) +} \ No newline at end of file diff --git a/src/app/components/dropDownList.tsx b/src/app/components/dropDownList.tsx index 8a3a9e5..36442ca 100644 --- a/src/app/components/dropDownList.tsx +++ b/src/app/components/dropDownList.tsx @@ -30,7 +30,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList callBack(childValue || ""); setIsOpen(false); }, - className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 text-[#6366f1]`, + className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 text-[#6366f1] cursor-pointer select-none`, key: index }); } diff --git a/src/app/styles/VKLogin.scss b/src/app/styles/VKLogin.scss new file mode 100644 index 0000000..18dd26e --- /dev/null +++ b/src/app/styles/VKLogin.scss @@ -0,0 +1,52 @@ +.VkIdWebSdk__button_reset { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; +} + +.VkIdWebSdk__button { + background: #0077ff; + cursor: pointer; + transition: all .1s ease-out; +} + +.VkIdWebSdk__button:hover { + opacity: 0.8; +} + +.VkIdWebSdk__button:active { + opacity: .7; + transform: scale(.97); +} + +.VkIdWebSdk__button { + border-radius: 8px; + width: 100%; + min-height: 44px; +} + +.VkIdWebSdk__button_container { + display: flex; + align-items: center; + padding: 12px 10px; +} + +.VkIdWebSdk__button_icon+.VkIdWebSdk__button_text { + margin-left: -28px; +} + +.VkIdWebSdk__button_text { + display: flex; + font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif; + flex: 1; + justify-content: center; + color: #ffffff; +} \ No newline at end of file diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index a967f4b..2828092 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -5,6 +5,7 @@ @use './reports.scss'; @use './marking-pages.scss'; @use './privacy-and-terms-pages.scss'; +@use './VKLogin.scss'; .btn { padding: 12px 24px; diff --git a/src/app/styles/marking-pages.scss b/src/app/styles/marking-pages.scss index 988b7f1..ffd72bc 100644 --- a/src/app/styles/marking-pages.scss +++ b/src/app/styles/marking-pages.scss @@ -186,7 +186,7 @@ } th { - background: linear-gradient(135deg, #6366f1, #8b5cf6); + background: linear-gradient(180deg, #6366f1, #8b5cf6); color: white; padding: 15px; text-align: left; diff --git a/src/app/ui/header/notificationsButton.tsx b/src/app/ui/header/notificationsButton.tsx index 1a4906a..66e7ead 100644 --- a/src/app/ui/header/notificationsButton.tsx +++ b/src/app/ui/header/notificationsButton.tsx @@ -65,7 +65,7 @@ export default function NotificationsButton() {

    -
    🪙 Токены добавлены
    +
    Токены добавлены
    На ваш баланс добавлено 100 токенов. Теперь вы можете защитить ещё больше файлов!
    4 дн назад
    From ee1575cac7a80c02e43b7b9c95fd80a828677cf1 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 10:11:39 +0700 Subject: [PATCH 07/17] reafactor + show password button for confirm password --- src/app/[locale]/login/page.tsx | 2 +- src/app/[locale]/register/page.tsx | 2 +- src/app/ui/{ => forms}/login-form.tsx | 0 src/app/ui/{ => forms}/register-form.tsx | 73 ++++++++++++++++++------ 4 files changed, 57 insertions(+), 20 deletions(-) rename src/app/ui/{ => forms}/login-form.tsx (100%) rename src/app/ui/{ => forms}/register-form.tsx (73%) diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx index 0848637..f6999f6 100644 --- a/src/app/[locale]/login/page.tsx +++ b/src/app/[locale]/login/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from 'next'; import styles from '@/app/styles/login.module.scss' import LogoIcon from '@/app/ui/logo-icon'; import Link from 'next/link'; -import LoginForm from '@/app/ui/login-form'; +import LoginForm from '@/app/ui/forms/login-form'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import { useTranslations } from 'next-intl'; import VKLogin from '@/app/components/VKLogin'; diff --git a/src/app/[locale]/register/page.tsx b/src/app/[locale]/register/page.tsx index 763202a..fa360a5 100644 --- a/src/app/[locale]/register/page.tsx +++ b/src/app/[locale]/register/page.tsx @@ -1,7 +1,7 @@ import LogoIcon from '@/app/ui/logo-icon'; import styles from '@/app/styles/login.module.scss' import Link from 'next/link'; -import RegisterForm from '@/app/ui/register-form'; +import RegisterForm from '@/app/ui/forms/register-form'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import {useTranslations} from 'next-intl'; diff --git a/src/app/ui/login-form.tsx b/src/app/ui/forms/login-form.tsx similarity index 100% rename from src/app/ui/login-form.tsx rename to src/app/ui/forms/login-form.tsx diff --git a/src/app/ui/register-form.tsx b/src/app/ui/forms/register-form.tsx similarity index 73% rename from src/app/ui/register-form.tsx rename to src/app/ui/forms/register-form.tsx index 48885b8..e527dfe 100644 --- a/src/app/ui/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -16,17 +16,31 @@ export default function RegisterForm() { const t = useTranslations('Login-register-form'); const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); - function handleMouseDown() { - setShowPassword(true); + function handleMouseDown(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(true); + } else { + setShowConfirmPassword(true); + } } - function handleMouseUp() { - setShowPassword(false); + function handleMouseUp(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(false); + } else { + setShowConfirmPassword(false); + } + } - function handleMouseOut() { - setShowPassword(false); + function handleMouseOut(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(false); + } else { + setShowConfirmPassword(false); + } } return ( @@ -115,11 +129,17 @@ export default function RegisterForm() { defaultValue={state?.previousState?.password ? state?.previousState?.password : ''} /> @@ -138,14 +158,31 @@ export default function RegisterForm() { - +
    + + +
    {state?.error?.confirm_password && (

    {t(state?.error?.confirm_password)} From 3d5748603059ff8b3c9adb10d9ac88d99f8952ef Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 10:17:26 +0700 Subject: [PATCH 08/17] add margin for diagram --- src/app/components/PieChartComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/PieChartComponent.tsx b/src/app/components/PieChartComponent.tsx index e6431a4..801a5e3 100644 --- a/src/app/components/PieChartComponent.tsx +++ b/src/app/components/PieChartComponent.tsx @@ -16,13 +16,13 @@ export const PieChartComponent = ({ data }: { > - + 0 {t('out-of')} - + 0 Date: Tue, 16 Dec 2025 11:24:05 +0700 Subject: [PATCH 09/17] cyrillic email regex --- src/app/actions/definitions.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index d5f03a2..4b68c1c 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -16,20 +16,28 @@ export type FormAction = ( formData: FormData ) => Promise +const emailRegex = /^([a-zA-Z0-9.\-_]+|[а-яА-ЯёЁ0-9.\-_]+)@(([a-zA-Z0-9.\-]+)\.([a-zA-Z]{2,})|([a-zA-Z0-9.\-]+)\.([а-яА-ЯёЁ]{2,})|([а-яА-ЯёЁ0-9.\-]+)\.([a-zA-Z]{2,})|([а-яА-ЯёЁ0-9.\-]+)\.([а-яА-ЯёЁ]{2,}))$/i; + export const SignupFormSchema = z .object({ full_name: z .string() .min(3, { error: 'register-error-name' }) .trim(), - email: z.email({ error: 'register-error-valid-email' }).trim(), + email: z + .string() + .trim() + .refine( + (value) => emailRegex.test(value), + { message: 'register-error-valid-email' } + ), phone: z .string() .min(12, { error: 'register-error-phone' }), password: z .string() .min(8, { error: 'register-error-password-symbols' }) - .regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'register-error-password-one-letter'}) + .regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'register-error-password-one-letter' }) .regex(/[0-9]/, { error: 'register-error-password-one-digit' }) .trim(), confirm_password: z From e94b0bf0413fe4bba7c09a9b3ee1c7e49d66b940 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 11:34:04 +0700 Subject: [PATCH 10/17] change tanstak table text color --- src/app/components/dropDownList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/dropDownList.tsx b/src/app/components/dropDownList.tsx index 36442ca..c992e02 100644 --- a/src/app/components/dropDownList.tsx +++ b/src/app/components/dropDownList.tsx @@ -30,7 +30,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList callBack(childValue || ""); setIsOpen(false); }, - className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 text-[#6366f1] cursor-pointer select-none`, + className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 cursor-pointer select-none`, key: index }); } @@ -43,7 +43,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList onClick={() => { setIsOpen(!isOpen) }} - className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium text-[#6366f1] bg-white border-2 border-[#6366f1] rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" + className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium bg-white border-2 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#6365f186]" > @@ -64,7 +64,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList {isOpen && ( -

    +
      Date: Tue, 16 Dec 2025 15:03:32 +0700 Subject: [PATCH 11/17] change marking page layout --- package.json | 2 +- src/app/[locale]/pages/marking-photo/page.tsx | 11 +- src/app/components/StackedBarChart.tsx | 204 ++++++++++++++++++ src/app/styles/marking-pages.scss | 17 +- src/app/ui/icons/icons.tsx | 12 +- .../ui/marking-page/protected-files-table.tsx | 2 + .../ui/marking-page/protection-summary.tsx | 97 +++++++-- .../ui/marking-page/upload-section-image.tsx | 17 +- src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 10 files changed, 340 insertions(+), 28 deletions(-) create mode 100644 src/app/components/StackedBarChart.tsx diff --git a/package.json b/package.json index e7a05ae..1d02bd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-photo/page.tsx index 9d2b8cf..3d23083 100644 --- a/src/app/[locale]/pages/marking-photo/page.tsx +++ b/src/app/[locale]/pages/marking-photo/page.tsx @@ -2,18 +2,23 @@ import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; import UploadSectionImage from '@/app/ui/marking-page/upload-section-image'; -import { IconImageFile } from '@/app/ui/icons/icons'; +import TanstakFilesTable from '@/app/components/tanstakTable'; +import { useTranslations } from 'next-intl'; export default function Page() { + const t = useTranslations('Global'); return (
      -

      Защита изображений

      +

      + {t('image-protection')} +

      - + + {/* */}
      ) } \ No newline at end of file diff --git a/src/app/components/StackedBarChart.tsx b/src/app/components/StackedBarChart.tsx new file mode 100644 index 0000000..c4ba70f --- /dev/null +++ b/src/app/components/StackedBarChart.tsx @@ -0,0 +1,204 @@ +import { useTranslations } from 'next-intl'; +import { BarChart, Bar, Cell, XAxis, Tooltip } from 'recharts'; + +type Files = { + data: { + name: string, + checks: number, + violations: number + }[] +} + +interface BarShapeProps { + x: number; + y: number; + width: number; + height: number; + fill: string; + radius?: number[]; +} + +export const StackedBarChart = ({ data }: Files) => { + const t = useTranslations('Global'); + + const editedData = data.map(e => { + return { + name: e.name, + checks: e.checks - e.violations, + violations: e.violations + } + }) + + const mainColors = ['#0088FE20', '#00C49F20', '#FFBB2820', '#FF804220', '#FF000020', '#FFC0CB20']; + const secondColors = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', 'red', 'pink']; + + const strokeColor = "#fff"; + const strokeWidth = 1; + + const renderBarWithPartialBorder = (props: BarShapeProps) => { + const { x, y, width, height, fill } = props; + + return ( + <> + {/* Основной прямоугольник без обводки */} + + + {/* Левая граница */} + + + {/* Правая граница */} + + + {/* Нижняя граница */} + + + ); + }; + + const renderBarWithTopBorder = (props: BarShapeProps) => { + const { x, y, width, height, fill } = props; + const borderRadius = 10; + + if (height <= 0 || width <= 0) return null; + + return ( + <> + {/* Основной прямоугольник с закруглением ТОЛЬКО ВЕРХНИХ углов */} + + + {/* Верхняя граница */} + + + {/* Левая граница */} + + + {/* Правая граница */} + + + {/* Левый верхний закругленный угол */} + + + {/* Правый верхний закругленный угол */} + + + ) as any; + }; + + return ( + + {/* */} + + + {data.map((_entry, index) => ( + + ))} + + + {data.map((_entry, index) => ( + + ))} + + + ); +}; \ No newline at end of file diff --git a/src/app/styles/marking-pages.scss b/src/app/styles/marking-pages.scss index ffd72bc..a36fb5c 100644 --- a/src/app/styles/marking-pages.scss +++ b/src/app/styles/marking-pages.scss @@ -61,19 +61,28 @@ margin-bottom: 20px; transition: all 0.3s ease; + .icon { + width: 70px; + height: 70px; + margin: 0 auto; + color: #6366f1; + } + h4 { color: #6366f1; - margin-bottom: 10px; + margin-bottom: 5px; + font-size: 22px; } .description { color: #6b7280; - margin-bottom: 20px; + margin-bottom: 5px; + text-align: start; } .btn-primary { - background: linear-gradient(45deg, #6566f1, #01579b); - margin-bottom: 20px; + background: linear-gradient(45deg, #6566f1, #5a5ce9); + margin-bottom: 40px; } } diff --git a/src/app/ui/icons/icons.tsx b/src/app/ui/icons/icons.tsx index dd5572d..8840b9c 100644 --- a/src/app/ui/icons/icons.tsx +++ b/src/app/ui/icons/icons.tsx @@ -26,7 +26,17 @@ export function IconAudioFile() { export function IconShield() { return ( - + + + + ) +} + +export function IconShieldAdd() { + return ( + + + ) } diff --git a/src/app/ui/marking-page/protected-files-table.tsx b/src/app/ui/marking-page/protected-files-table.tsx index 987854f..e968f0a 100644 --- a/src/app/ui/marking-page/protected-files-table.tsx +++ b/src/app/ui/marking-page/protected-files-table.tsx @@ -1,3 +1,5 @@ +/* removed */ + import { IconShield, IconDownload } from '@/app/ui/icons/icons'; export default function ProtectedFilesTable() { return ( diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index 30a560b..8bda875 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -1,21 +1,92 @@ +'use client' + +import { useState } from 'react'; +import { StackedBarChart } from '@/app/components/StackedBarChart'; +import { useTranslations } from 'next-intl'; + +const data = [ + { + name: 'Page A', + checks: 4000, + violations: 2000 + }, + { + name: 'Page B', + checks: 3000, + violations: 1398 + }, + { + name: 'Page C', + checks: 2000, + violations: 1000 + }, + { + name: 'Page D', + checks: 2780, + violations: 1000 + }, + { + name: 'Page E', + checks: 5090, + violations: 4800 + }, + { + name: 'Page F', + checks: 5390, + violations: 3800 + } +]; + export default function ProtectionSummary() { + const [isOpen, setIsOpen] = useState(false); + const t = useTranslations('Global'); + return ( -
      -

      Статистика защиты

      -
      -
      -
      0
      -
      Всего файлов
      +
      +

      {t('protecting-your-content')}

      +

      {t('current-status-of')}

      +
      +
      +
      0
      +
      {t('total-files')}
      -
      -
      0
      -
      Общий размер
      -
      -
      -
      0
      -
      Проверки/нарушения
      + +
      +
      0
      +
      {t('size')}
      + {isOpen ? ( +
      + +
      + ) : ( +
      +
      0
      +
      + {t('size')} +
      +
      + )}
      +
      ) } \ No newline at end of file diff --git a/src/app/ui/marking-page/upload-section-image.tsx b/src/app/ui/marking-page/upload-section-image.tsx index 1cf0bf0..fba6005 100644 --- a/src/app/ui/marking-page/upload-section-image.tsx +++ b/src/app/ui/marking-page/upload-section-image.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react'; +import {IconShieldAdd} from '@/app/ui/icons/icons'; interface SelectedFile { file: File; @@ -188,11 +189,9 @@ export default function UploadSectionImage() { transition: 'all .2s ease-in-out' }} > +

      Перетащите изображения сюда

      -

      - Размер: 150x150 - 4000x4000 пикселей, форматы: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается) -

      - +

      или

      +

      + Разрешение изображение: 100x100 - 10000x10000 пикселей +

      +

      + Размер файла: до 20 МБ +

      +

      + Формат файла: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается) +

      + {error && (

      ❌ {error}

      diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index bfddbfb..82831e7 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -53,7 +53,8 @@ "shown": "Shown", "date-filter": "Date filter", "type-filter": "Type filter", - "items-per-page": "Items per page" + "items-per-page": "Items per page", + "image-protection": "Защита изображений" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index e4adcc1..041ad37 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -53,7 +53,8 @@ "shown": "Показано", "date-filter": "Фильтр по дате", "type-filter": "Фильтр по типу", - "items-per-page": "Записей на странице" + "items-per-page": "Записей на странице", + "image-protection": "Защита изображений" }, "Login-register-form": { "and": "и", From 169d0d33a0f54f4206e4359406e4dceb567e8d3b Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 20:26:17 +0700 Subject: [PATCH 12/17] update translate and styles --- src/app/[locale]/pages/marking-audio/page.tsx | 5 +- src/app/[locale]/pages/marking-photo/page.tsx | 4 +- src/app/[locale]/pages/marking-video/page.tsx | 4 +- src/app/[locale]/pages/settings/page.tsx | 19 +++-- src/app/components/StackedBarChart.tsx | 9 +- src/app/components/dropDownList.tsx | 4 +- src/app/components/tanstakTable.tsx | 82 ++++++++++--------- src/app/styles/global-styles.scss | 14 ++++ .../ui/marking-page/protection-summary.tsx | 21 ++++- .../ui/settings/personal-data-settings.tsx | 53 ++++++++---- src/i18n/messages/en.json | 17 +++- src/i18n/messages/ru.json | 17 +++- 12 files changed, 171 insertions(+), 78 deletions(-) diff --git a/src/app/[locale]/pages/marking-audio/page.tsx b/src/app/[locale]/pages/marking-audio/page.tsx index 8fcfa94..9d86497 100644 --- a/src/app/[locale]/pages/marking-audio/page.tsx +++ b/src/app/[locale]/pages/marking-audio/page.tsx @@ -1,7 +1,6 @@ -import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; -import { IconAudioFile } from '@/app/ui/icons/icons'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { return ( @@ -11,7 +10,7 @@ export default function Page() {
      - +
      ) } \ No newline at end of file diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-photo/page.tsx index 3d23083..ddd637e 100644 --- a/src/app/[locale]/pages/marking-photo/page.tsx +++ b/src/app/[locale]/pages/marking-photo/page.tsx @@ -2,8 +2,8 @@ import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; import UploadSectionImage from '@/app/ui/marking-page/upload-section-image'; -import TanstakFilesTable from '@/app/components/tanstakTable'; import { useTranslations } from 'next-intl'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { const t = useTranslations('Global'); @@ -17,7 +17,7 @@ export default function Page() { - + {/* */}
      ) diff --git a/src/app/[locale]/pages/marking-video/page.tsx b/src/app/[locale]/pages/marking-video/page.tsx index 53fcd68..9d9254e 100644 --- a/src/app/[locale]/pages/marking-video/page.tsx +++ b/src/app/[locale]/pages/marking-video/page.tsx @@ -1,7 +1,7 @@ -import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; import UploadSectionVideo from '@/app/ui/marking-page/upload-section-video'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { @@ -13,7 +13,7 @@ export default function Page() { - +
      ) } \ No newline at end of file diff --git a/src/app/[locale]/pages/settings/page.tsx b/src/app/[locale]/pages/settings/page.tsx index 2143810..fa7f71b 100644 --- a/src/app/[locale]/pages/settings/page.tsx +++ b/src/app/[locale]/pages/settings/page.tsx @@ -5,20 +5,25 @@ import SubscriptionSettings from '@/app/ui/settings/subscription-settings'; import APISettings from '@/app/ui/settings/api-settings'; import LastActivitySettings from '@/app/ui/settings/last-activity-settings'; import DangerZone from '@/app/ui/settings/danger-zone'; +import { useTranslations } from 'next-intl'; export default function Page() { + const t = useTranslations('Global'); + return ( <> -

      ⚙️ Настройки аккаунта

      +

      + {t('account-settings')} +

      - - - - - + + + + +
      - + ) } \ No newline at end of file diff --git a/src/app/components/StackedBarChart.tsx b/src/app/components/StackedBarChart.tsx index c4ba70f..71f8e24 100644 --- a/src/app/components/StackedBarChart.tsx +++ b/src/app/components/StackedBarChart.tsx @@ -32,7 +32,7 @@ export const StackedBarChart = ({ data }: Files) => { const mainColors = ['#0088FE20', '#00C49F20', '#FFBB2820', '#FF804220', '#FF000020', '#FFC0CB20']; const secondColors = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', 'red', 'pink']; - const strokeColor = "#fff"; + const strokeColor = "#bfbdbdd1"; const strokeWidth = 1; const renderBarWithPartialBorder = (props: BarShapeProps) => { @@ -163,7 +163,7 @@ export const StackedBarChart = ({ data }: Files) => { return ( { {data.map((_entry, index) => ( @@ -192,7 +193,7 @@ export const StackedBarChart = ({ data }: Files) => { {data.map((_entry, index) => ( diff --git a/src/app/components/dropDownList.tsx b/src/app/components/dropDownList.tsx index c992e02..098c212 100644 --- a/src/app/components/dropDownList.tsx +++ b/src/app/components/dropDownList.tsx @@ -43,7 +43,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList onClick={() => { setIsOpen(!isOpen) }} - className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium bg-white border-2 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#6365f186]" + className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium bg-white border-2 border-[#d1cece] rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#6365f186]" > @@ -64,7 +64,7 @@ export default function DropDownList({ children, value, callBack }: DropDownList {isOpen && ( -
      +
        {/* Таблица */} -
        - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.length > 0 ? ( - table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - +
        +
        +
        - {header.isPlaceholder - ? null - : typeof header.column.columnDef.header === 'function' - ? header.column.columnDef.header(header.getContext()) - : header.column.columnDef.header as string} -
        - {typeof cell.column.columnDef.cell === 'function' - ? cell.column.columnDef.cell(cell.getContext()) - : cell.getValue() as string} -
        + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + ))} - )) - ) : ( - - - - )} - -
        + {header.isPlaceholder + ? null + : typeof header.column.columnDef.header === 'function' + ? header.column.columnDef.header(header.getContext()) + : header.column.columnDef.header as string} +
        - {t('no-data-for-selected-filters')} -
        + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + {typeof cell.column.columnDef.cell === 'function' + ? cell.column.columnDef.cell(cell.getContext()) + : cell.getValue() as string} + + ))} + + )) + ) : ( + + + {t('no-data-for-selected-filters')} + + + )} + + +
      {/* Пагинация */} diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 2828092..2cb73d0 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -112,4 +112,18 @@ color: black; } } +} + +@media (max-width: 1300px) { + .tanstak-file-table { + max-width: 800px; + margin: 0 auto; + } +} + +@media (max-width: 1200px) { + .tanstak-file-table { + max-width: 500px; + margin: 0 auto; + } } \ No newline at end of file diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index 8bda875..757366e 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -52,8 +52,25 @@ export default function ProtectionSummary() {
      -
      0
      -
      {t('size')}
      + {isOpen ? ( + <> +
      0
      +
      + {t('size')} +
      + + ) : + <> +
      +
      0
      +
      {t('check')}
      +
      +
      +
      0
      +
      {t('violations')}
      +
      + + }
      {isOpen ? (
      diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index fb659c4..61128e8 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl'; import { useActionState, useState } from 'react' export default function PersonalDataSettings() { @@ -12,58 +13,82 @@ export default function PersonalDataSettings() { const [selectedValue, setSelectedValue] = useState('male'); + const t = useTranslations('Global'); + return (
      -

      👤 Персональные данные

      +

      + {t('personal-data')} +

      - +
      - +
      - + - Email нельзя изменить. Обратитесь в поддержку. + {t('email-cant-change')}
      - +
      - +
      - + - Используется для отправки поздравления + {t('used-to-send-congratulations')}
      - + - Заполняется автоматически исходя из даты рождения + {t('automatically-filled-in-based-on-date-of-birth')}
      - +
      diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 82831e7..66a94c1 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -54,7 +54,22 @@ "date-filter": "Date filter", "type-filter": "Type filter", "items-per-page": "Items per page", - "image-protection": "Защита изображений" + "email": "Email", + "image-protection": "Защита изображений", + "account-settings": "Account settings", + "personal-data": "Personal data", + "full-name": "Full name", + "company": "Company", + "email-cant-change": "Email address cannot be changed. Please contact support.", + "phone": "Phone", + "gender": "Gender", + "birthday": "Birthday", + "age": "Age", + "male": "Male", + "female": "Female", + "used-to-send-congratulations": "Used to send congratulations", + "automatically-filled-in-based-on-date-of-birth": "Automatically filled in based on date of birth", + "save-changes": "Save changes" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 041ad37..2ddde03 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -54,7 +54,22 @@ "date-filter": "Фильтр по дате", "type-filter": "Фильтр по типу", "items-per-page": "Записей на странице", - "image-protection": "Защита изображений" + "email": "Почта", + "image-protection": "Защита изображений", + "account-settings": "Настройки аккаунта", + "personal-data": "Персональные данные", + "full-name": "Полное имя", + "company": "Компания", + "email-cant-change": "Email нельзя изменить. Обратитесь в поддержку.", + "phone": "Телефон", + "gender": "Пол", + "birthday": "День рождения", + "age": "Возраст", + "male": "Мужской", + "female": "Женский", + "used-to-send-congratulations": "Используется для отправки поздравления", + "automatically-filled-in-based-on-date-of-birth": "Заполняется автоматически исходя из даты рождения", + "save-changes": "Сохранить изменения" }, "Login-register-form": { "and": "и", From 3b32b3858669bf1f9e14dfc5955c27e6c43b49a4 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 20:33:06 +0700 Subject: [PATCH 13/17] border color for stacked bar --- src/app/components/StackedBarChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/StackedBarChart.tsx b/src/app/components/StackedBarChart.tsx index 71f8e24..ef9af12 100644 --- a/src/app/components/StackedBarChart.tsx +++ b/src/app/components/StackedBarChart.tsx @@ -32,7 +32,7 @@ export const StackedBarChart = ({ data }: Files) => { const mainColors = ['#0088FE20', '#00C49F20', '#FFBB2820', '#FF804220', '#FF000020', '#FFC0CB20']; const secondColors = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', 'red', 'pink']; - const strokeColor = "#bfbdbdd1"; + const strokeColor = "#dfdede24"; const strokeWidth = 1; const renderBarWithPartialBorder = (props: BarShapeProps) => { From 6e4dceda0c6b6f1d8156eb24590e74f62d60586e Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 16 Dec 2025 21:04:55 +0700 Subject: [PATCH 14/17] set start number 7 for phone mask --- src/app/ui/inputs/phone-input.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/ui/inputs/phone-input.tsx b/src/app/ui/inputs/phone-input.tsx index 9c0faf0..61c3a6a 100644 --- a/src/app/ui/inputs/phone-input.tsx +++ b/src/app/ui/inputs/phone-input.tsx @@ -22,8 +22,8 @@ export default function PhoneInput({ phoneState }: PhoneInputProps) { } const limitedDigits = digitsOnly.slice(0, 11); - const countryCode = limitedDigits.startsWith('8') ? '8' : '7'; - const phoneNumber = limitedDigits.startsWith('8') || limitedDigits.startsWith('7') + const countryCode = 7; + const phoneNumber = limitedDigits.startsWith('7') ? limitedDigits.slice(1) : limitedDigits; From c42d5eae276bd4de984ce9777f8e903b2ce5df36 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 17 Dec 2025 09:18:18 +0700 Subject: [PATCH 15/17] fix page highlight --- src/app/ui/nav-link-dropdown.tsx | 4 +++- src/app/ui/nav-links.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/ui/nav-link-dropdown.tsx b/src/app/ui/nav-link-dropdown.tsx index 53008cc..70cc2e5 100644 --- a/src/app/ui/nav-link-dropdown.tsx +++ b/src/app/ui/nav-link-dropdown.tsx @@ -6,7 +6,9 @@ import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; export default function DropDownList() { - const pathname = usePathname(); + const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); + console.log('pathname'); + console.log(pathname); const [dropDownExpanded, setDropDownExpanded] = useState(false); const t = useTranslations('Global'); diff --git a/src/app/ui/nav-links.tsx b/src/app/ui/nav-links.tsx index 4d7aebe..09163e3 100644 --- a/src/app/ui/nav-links.tsx +++ b/src/app/ui/nav-links.tsx @@ -10,7 +10,7 @@ import { logout } from '@/app/actions/auth'; import { useTranslations } from 'next-intl'; export default function NavLinks() { - const pathname = usePathname(); + const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); const t = useTranslations('Global'); const links = [ From 70ebf066631f0c3893c74db8369a4ed9afc72008 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 17 Dec 2025 12:50:01 +0700 Subject: [PATCH 16/17] fix stacked bar shar diagram --- src/app/components/StackedBarChart.tsx | 134 ++++---- src/app/styles/global-styles.scss | 3 +- src/app/styles/marking-pages.scss | 293 ---------------- .../{dashboard.scss => pages-styles.scss} | 312 ++++++++++++++++++ .../ui/marking-page/protection-summary.tsx | 37 ++- 5 files changed, 398 insertions(+), 381 deletions(-) delete mode 100644 src/app/styles/marking-pages.scss rename src/app/styles/{dashboard.scss => pages-styles.scss} (50%) diff --git a/src/app/components/StackedBarChart.tsx b/src/app/components/StackedBarChart.tsx index ef9af12..4ea4ecf 100644 --- a/src/app/components/StackedBarChart.tsx +++ b/src/app/components/StackedBarChart.tsx @@ -21,62 +21,56 @@ interface BarShapeProps { export const StackedBarChart = ({ data }: Files) => { const t = useTranslations('Global'); - const editedData = data.map(e => { - return { - name: e.name, - checks: e.checks - e.violations, - violations: e.violations - } - }) - - const mainColors = ['#0088FE20', '#00C49F20', '#FFBB2820', '#FF804220', '#FF000020', '#FFC0CB20']; - const secondColors = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', 'red', 'pink']; - const strokeColor = "#dfdede24"; const strokeWidth = 1; const renderBarWithPartialBorder = (props: BarShapeProps) => { const { x, y, width, height, fill } = props; + const borderRadius = 10; return ( <> - {/* Основной прямоугольник без обводки */} + {/* Основной прямоугольник с закругленными углами */} - {/* Левая граница */} + {/* Левая граница (центральная часть, без закруглений) */} - {/* Правая граница */} + {/* Правая граница (центральная часть, без закруглений) */} - {/* Нижняя граница */} + {/* Нижняя граница (прямая часть между закруглениями) */} + + {/* Верхняя граница (прямая часть между закруглениями) */} + ); @@ -90,82 +84,66 @@ export const StackedBarChart = ({ data }: Files) => { return ( <> - {/* Основной прямоугольник с закруглением ТОЛЬКО ВЕРХНИХ углов */} - - {/* Верхняя граница */} - - - {/* Левая граница */} + {/* Левая граница (центральная часть, без закруглений) */} - {/* Правая граница */} + {/* Правая граница (центральная часть, без закруглений) */} + + {/* Нижняя граница (прямая часть между закруглениями) */} + - {/* Левый верхний закругленный угол */} - - - {/* Правый верхний закругленный угол */} - - ) as any; + ); }; return ( { dataKey="checks" stackId="a" label={{ position: 'top', fill: '#fff', fontSize: 12 }} + radius={5} shape={renderBarWithPartialBorder as any} > {data.map((_entry, index) => ( - + ))} {data.map((_entry, index) => ( - + ))} diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 2cb73d0..856c77a 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -1,9 +1,8 @@ @use './header.scss'; -@use './dashboard.scss'; +@use './pages-styles.scss'; @use './settings.scss'; @use './payment-container.scss'; @use './reports.scss'; -@use './marking-pages.scss'; @use './privacy-and-terms-pages.scss'; @use './VKLogin.scss'; diff --git a/src/app/styles/marking-pages.scss b/src/app/styles/marking-pages.scss deleted file mode 100644 index a36fb5c..0000000 --- a/src/app/styles/marking-pages.scss +++ /dev/null @@ -1,293 +0,0 @@ -.protection-summary { - background: linear-gradient(135deg, #e8f5e8, #c8e6c9); - border: 2px solid #4caf50; - border-radius: 15px; - padding: 20px; - margin-bottom: 25px; - text-align: center; - - h3 { - color: #2e7d32; - margin-bottom: 15px; - font-size: 18px; - } - - .protection-stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 15px; - margin-top: 15px; - } - - .stat-card { - background: rgba(255, 255, 255, 0.8); - padding: 15px; - border-radius: 10px; - text-align: center; - } - - .stat-number { - font-size: 24px; - font-weight: bold; - color: #2e7d32; - } - - .stat-label { - font-size: 12px; - color: #666; - margin-top: 5px; - } -} - -.upload-section { - background: white; - border-radius: 20px; - padding: 30px; - margin-bottom: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - margin-bottom: 20px; - color: #111827; - font-size: 20px; - } - - .drag-drop-zone { - border: 3px dashed #6366f1; - border-radius: 15px; - padding: 40px 20px; - text-align: center; - background: linear-gradient(135deg, #f0f9ff, #e0f2fe); - margin-bottom: 20px; - transition: all 0.3s ease; - - .icon { - width: 70px; - height: 70px; - margin: 0 auto; - color: #6366f1; - } - - h4 { - color: #6366f1; - margin-bottom: 5px; - font-size: 22px; - } - - .description { - color: #6b7280; - margin-bottom: 5px; - text-align: start; - } - - .btn-primary { - background: linear-gradient(45deg, #6566f1, #5a5ce9); - margin-bottom: 40px; - } - } - - .selected-file { - border-radius: 15px; - background: #fff; - padding: 10px; - - &-file-info { - text-align: left; - } - - .uploaded-image { - border-radius: 0.25rem; - max-height: calc(4px * 40); - margin-inline: auto; - margin-bottom: 20px; - border: 1px solid #d1d5db; - } - - .btn-confirm { - background: linear-gradient(45deg, #6566f1, #01579b); - color: white; - border: none; - padding: 5px 15px; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - font-size: 16px; - transition: 0.3s; - min-width: 160px; - transform: translateY(0px); - box-shadow: none; - } - - .btn-cancel { - background: #fff; - color: rgb(99, 102, 241); - border: 2px solid rgb(99, 102, 241); - padding: 5px 15px; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - font-size: 16px; - transition: 0.3s; - min-width: 120px; - } - - .btn:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px #6366f14d; - } - } -} - -.test-section { - background: linear-gradient(135deg, #fff3e0, #ffe0b2); - border-radius: 20px; - padding: 30px; - margin-bottom: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - margin-bottom: 20px; - color: #e65100; - font-size: 20px; - } - - .test-section-content { - border: 2px dashed #ff9800; - border-radius: 10px; - padding: 20px; - text-align: center; - background: #fff8e1; - - h4 { - font-size: 32px; - margin-bottom: 10px; - } - - .btn { - background: linear-gradient(45deg, #ff9800, #f57c00); - } - - p { - margin-top: 15px; - font-size: 12px; - color: #e65100; - } - } -} - -.protected-files-table { - background: white; - border-radius: 15px; - overflow: hidden; - margin-top: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - padding: 20px 20px 0; - color: #111827; - font-size: 18px; - margin-bottom: 20px; - } - - table { - width: 100%; - border-collapse: collapse; - } - - th { - background: linear-gradient(180deg, #6366f1, #8b5cf6); - color: white; - padding: 15px; - text-align: left; - font-weight: 600; - font-size: 14px; - } - - td { - padding: 15px; - border-bottom: 1px solid #f3f4f6; - vertical-align: middle; - } - - .files-table { - &-file { - display: flex; - align-items: center; - } - - &-icon { - width: 40px; - height: 40px; - background: #f0f9ff; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - margin-right: 12px; - border: 2px solid #6366f1; - color: #6366f1; - } - - &-title { - font-weight: 500; - color: #111827; - font-size: 14px; - } - - &-protect { - font-size: 11px; - color: #6366f1; - font-weight: 600; - } - - &-size { - color: #6b7280; - font-size: 14px; - } - - &-protect { - display: flex; - align-items: center; - } - - &-bar { - width: 30px; - height: 6px; - background: #e5e7eb; - border-radius: 3px; - margin-right: 8px; - } - - &-fill { - width: 100%; - height: 100%; - background: #6366f1; - border-radius: 3px; - } - - &-date { - color: #6b7280; - font-size: 13px; - } - } - - .btn-secondary { - background: #6366f1; - color: white; - padding: 5px 16px 5px 10px; - } - - .icon { - height: 18px; - } - - .real-protection-badge { - background: linear-gradient(135deg, #6366f1, #8b5cf6); - color: white; - padding: 2px 6px; - border-radius: 4px; - font-size: 9px; - font-weight: bold; - margin-left: 5px; - } -} \ No newline at end of file diff --git a/src/app/styles/dashboard.scss b/src/app/styles/pages-styles.scss similarity index 50% rename from src/app/styles/dashboard.scss rename to src/app/styles/pages-styles.scss index 056c1c4..62d4ba4 100644 --- a/src/app/styles/dashboard.scss +++ b/src/app/styles/pages-styles.scss @@ -118,6 +118,24 @@ font-size: 14px; opacity: 0.9; } + + &-total-info { + position: absolute; + top: 10; + left: 10; + background-color: #1f293718; + padding: 10px; + border-radius: 10; + font-size: 15px; + } + + .stacked-bar-chart { + border-radius: 10; + width: 100%; + padding: 0 20px; + display: flex; + justify-content: center; + } } &-switch { @@ -340,3 +358,297 @@ } } } + +.protection-summary { + background: linear-gradient(135deg, #e8f5e8, #c8e6c9); + border: 2px solid #4caf50; + border-radius: 15px; + padding: 20px; + margin-bottom: 25px; + text-align: center; + + h3 { + color: #2e7d32; + margin-bottom: 15px; + font-size: 18px; + } + + .protection-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + margin-top: 15px; + } + + .stat-card { + background: rgba(255, 255, 255, 0.8); + padding: 15px; + border-radius: 10px; + text-align: center; + } + + .stat-number { + font-size: 24px; + font-weight: bold; + color: #2e7d32; + } + + .stat-label { + font-size: 12px; + color: #666; + margin-top: 5px; + } +} + +.upload-section { + background: white; + border-radius: 20px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + margin-bottom: 20px; + color: #111827; + font-size: 20px; + } + + .drag-drop-zone { + border: 3px dashed #6366f1; + border-radius: 15px; + padding: 40px 20px; + text-align: center; + background: linear-gradient(135deg, #f0f9ff, #e0f2fe); + margin-bottom: 20px; + transition: all 0.3s ease; + + .icon { + width: 70px; + height: 70px; + margin: 0 auto; + color: #6366f1; + } + + h4 { + color: #6366f1; + margin-bottom: 5px; + font-size: 22px; + } + + .description { + color: #6b7280; + margin-bottom: 5px; + text-align: start; + } + + .btn-primary { + background: linear-gradient(45deg, #6566f1, #5a5ce9); + margin-bottom: 40px; + } + } + + .selected-file { + border-radius: 15px; + background: #fff; + padding: 10px; + + &-file-info { + text-align: left; + } + + .uploaded-image { + border-radius: 0.25rem; + max-height: calc(4px * 40); + margin-inline: auto; + margin-bottom: 20px; + border: 1px solid #d1d5db; + } + + .btn-confirm { + background: linear-gradient(45deg, #6566f1, #01579b); + color: white; + border: none; + padding: 5px 15px; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: 0.3s; + min-width: 160px; + transform: translateY(0px); + box-shadow: none; + } + + .btn-cancel { + background: #fff; + color: rgb(99, 102, 241); + border: 2px solid rgb(99, 102, 241); + padding: 5px 15px; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: 0.3s; + min-width: 120px; + } + + .btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px #6366f14d; + } + } +} + +.test-section { + background: linear-gradient(135deg, #fff3e0, #ffe0b2); + border-radius: 20px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + margin-bottom: 20px; + color: #e65100; + font-size: 20px; + } + + .test-section-content { + border: 2px dashed #ff9800; + border-radius: 10px; + padding: 20px; + text-align: center; + background: #fff8e1; + + h4 { + font-size: 32px; + margin-bottom: 10px; + } + + .btn { + background: linear-gradient(45deg, #ff9800, #f57c00); + } + + p { + margin-top: 15px; + font-size: 12px; + color: #e65100; + } + } +} + +.protected-files-table { + background: white; + border-radius: 15px; + overflow: hidden; + margin-top: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + padding: 20px 20px 0; + color: #111827; + font-size: 18px; + margin-bottom: 20px; + } + + table { + width: 100%; + border-collapse: collapse; + } + + th { + background: linear-gradient(180deg, #6366f1, #8b5cf6); + color: white; + padding: 15px; + text-align: left; + font-weight: 600; + font-size: 14px; + } + + td { + padding: 15px; + border-bottom: 1px solid #f3f4f6; + vertical-align: middle; + } + + .files-table { + &-file { + display: flex; + align-items: center; + } + + &-icon { + width: 40px; + height: 40px; + background: #f0f9ff; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + border: 2px solid #6366f1; + color: #6366f1; + } + + &-title { + font-weight: 500; + color: #111827; + font-size: 14px; + } + + &-protect { + font-size: 11px; + color: #6366f1; + font-weight: 600; + } + + &-size { + color: #6b7280; + font-size: 14px; + } + + &-protect { + display: flex; + align-items: center; + } + + &-bar { + width: 30px; + height: 6px; + background: #e5e7eb; + border-radius: 3px; + margin-right: 8px; + } + + &-fill { + width: 100%; + height: 100%; + background: #6366f1; + border-radius: 3px; + } + + &-date { + color: #6b7280; + font-size: 13px; + } + } + + .btn-secondary { + background: #6366f1; + color: white; + padding: 5px 16px 5px 10px; + } + + .icon { + height: 18px; + } + + .real-protection-badge { + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + padding: 2px 6px; + border-radius: 4px; + font-size: 9px; + font-weight: bold; + margin-left: 5px; + } +} \ No newline at end of file diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index 757366e..2981eec 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -6,34 +6,39 @@ import { useTranslations } from 'next-intl'; const data = [ { - name: 'Page A', + name: 'File A', checks: 4000, violations: 2000 }, { - name: 'Page B', + name: 'File B', checks: 3000, - violations: 1398 + violations: 1400 }, { - name: 'Page C', + name: 'File C', checks: 2000, violations: 1000 }, { - name: 'Page D', + name: 'File D', checks: 2780, violations: 1000 }, { - name: 'Page E', + name: 'File E', checks: 5090, violations: 4800 }, { - name: 'Page F', + name: 'File F', checks: 5390, violations: 3800 + }, + { + name: 'File H', + checks: 4000, + violations: 1500 } ]; @@ -73,8 +78,22 @@ export default function ProtectionSummary() { }
      {isOpen ? ( -
      - +
      +
      + +
      +
      +
      + {t('check')}: 777 +
      +
      + {t('violations')}: 777 +
      +
      ) : (
      From 8748a4b3ea4e30719c2aafb91c1334c28612d716 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 17 Dec 2025 12:52:55 +0700 Subject: [PATCH 17/17] remove TestSection from layout --- src/app/[locale]/pages/marking-audio/page.tsx | 2 +- src/app/[locale]/pages/marking-photo/page.tsx | 2 +- src/app/[locale]/pages/marking-video/page.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/[locale]/pages/marking-audio/page.tsx b/src/app/[locale]/pages/marking-audio/page.tsx index 9d86497..64a16b7 100644 --- a/src/app/[locale]/pages/marking-audio/page.tsx +++ b/src/app/[locale]/pages/marking-audio/page.tsx @@ -9,7 +9,7 @@ export default function Page() {

      Защита аудио

      - + {/* */}
      ) diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-photo/page.tsx index ddd637e..1c1b711 100644 --- a/src/app/[locale]/pages/marking-photo/page.tsx +++ b/src/app/[locale]/pages/marking-photo/page.tsx @@ -16,7 +16,7 @@ export default function Page() {
      - + {/* */} {/* */}
    diff --git a/src/app/[locale]/pages/marking-video/page.tsx b/src/app/[locale]/pages/marking-video/page.tsx index 9d9254e..3f5c730 100644 --- a/src/app/[locale]/pages/marking-video/page.tsx +++ b/src/app/[locale]/pages/marking-video/page.tsx @@ -12,7 +12,7 @@ export default function Page() {
    - + {/* */}
    )