diff --git a/package.json b/package.json index f45dbe7..447c884 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.108.0", + "version": "0.109.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/[locale]/pages/tracking/page.tsx b/src/app/[locale]/pages/tracking/page.tsx index 5031a43..9dba6bd 100644 --- a/src/app/[locale]/pages/tracking/page.tsx +++ b/src/app/[locale]/pages/tracking/page.tsx @@ -2,6 +2,7 @@ import { TrackingStatistic } from '@/app/ui/tracking-page/tracking-statistic'; import FilesTable from '@/app/ui/dashboard/files-table'; +import { TrackingHistoryView } from '@/app/ui/tracking-page/tracking-history-view'; export default async function Page() { const FILE_TYPE = "document"; @@ -12,6 +13,7 @@ export default async function Page() { > + ) } \ No newline at end of file diff --git a/src/app/actions/trackingActions.ts b/src/app/actions/trackingActions.ts index ecbd7c3..7bf5735 100644 --- a/src/app/actions/trackingActions.ts +++ b/src/app/actions/trackingActions.ts @@ -158,6 +158,44 @@ export async function filePermisionChange(fileId: string, permissions: boolean) return false; } + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchHistoryView(page: number, size: number) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30028, + message_body: { + token: token, + action: 'history_view', + page: page, + size: size + } + }), + headers: { + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const parsed = await response.json(); + + if (parsed.message_code === 0) { + return parsed.message_body; + } else { + return null; + } + } else { throw new Error(`${response.status}`); } diff --git a/src/app/api/auth/yandex/callback/route.ts b/src/app/api/auth/yandex/callback/route.ts deleted file mode 100644 index 7daf57b..0000000 --- a/src/app/api/auth/yandex/callback/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NextResponse } from 'next/server'; - -export async function POST(request: Request) { - const { code } = await request.json(); - - try { - const response = await fetch('https://oauth.yandex.ru/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - code, - client_id: 'b356a7ceac0f4c50a3b434220f86bce0', - client_secret: '0800f7774f7d4e19ad4b434f10afd4ab', - }) - }); - - const data = await response.json(); - - console.log(data); - - if (data.access_token) { - const userInfo = await fetch('https://login.yandex.ru/info?format=json', { - headers: { 'Authorization': `OAuth ${data.access_token}` } - }).then(r => r.json()); - - console.log(userInfo); - - // Тут мне нужно будет отправить сделать запрос в бек с userInfo и там уже редиректить на дешборд - - return NextResponse.json({ success: true }); - } - } catch (error) { - console.error('Token exchange failed:', error); - } - - return NextResponse.json({ success: false }, { status: 400 }); -} \ No newline at end of file diff --git a/src/app/hooks/react-query/useHistoryView.ts b/src/app/hooks/react-query/useHistoryView.ts new file mode 100644 index 0000000..6538b46 --- /dev/null +++ b/src/app/hooks/react-query/useHistoryView.ts @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchHistoryView } from '@/app/actions/trackingActions'; + +export interface HistoryViewItem { + city: string; + country: string; + viewer: string; + document: string; + view_date: string; +} + +export interface UseHistoryView { + documentsCount: number; + history: HistoryViewItem[]; + pageNumber: number; + pageSize: number; + totalElements: number; + totalPages: number; + uniqueCityCount: number; + uniqueCountryCount: number; +} + +export const useHistoryView = (page: number, size: number) => { + return useQuery({ + queryKey: ['violationStatistic'], + queryFn: () => { + return fetchHistoryView(page, size) + }, + select: (data: UseHistoryView | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/ui/tracking-page/tracking-history-view.tsx b/src/app/ui/tracking-page/tracking-history-view.tsx new file mode 100644 index 0000000..21f11d1 --- /dev/null +++ b/src/app/ui/tracking-page/tracking-history-view.tsx @@ -0,0 +1,418 @@ +'use client' + +import { useHistoryView, HistoryViewItem } from '@/app/hooks/react-query/useHistoryView'; +import { useEffect, useState, useMemo } from 'react'; +import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck, IconLink } from '@/app/ui/icons/icons'; +import DropDownList from '@/app/components/DropDownList'; +import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, SortingState, ColumnDef } from '@tanstack/react-table'; +import { formatDate, formatDateTime } from '@/app/lib/formatDate'; +import { useTranslations } from 'next-intl'; +import { useViewport } from '@/app/hooks/useViewport'; + +export function TrackingHistoryView() { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + const [sorting, setSorting] = useState([]); + const t = useTranslations('Global'); + + const { + data: apiResponse, + isLoading, + isFetching, + isError, + error, + refetch + } = useHistoryView(pagination.pageIndex, pagination.pageSize); + + useEffect(() => { + console.log(apiResponse); + }, [apiResponse]); + + const tableData = apiResponse?.history || []; + const totalItems = apiResponse?.history.length || 0; + const totalPages = Math.ceil(totalItems / pagination.pageSize); + + const viewport = useViewport(); + + const getMaxLengthByWidth = (): number => { + if (viewport.device === 'MOBILE') return 22; + if (viewport.device === 'TABLET') return 30; + if (viewport.device === 'DESKTOP') return 50; + return 100; + }; + + const cutFileName = (fileName: string) => { + const MAX_FILE_NAME_LENGTH = getMaxLengthByWidth(); + const lastDotIndex = fileName.lastIndexOf('.'); + + if (lastDotIndex <= 0) { + return fileName.length > MAX_FILE_NAME_LENGTH ? fileName.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : fileName; + } + + const nameWithoutExtension = fileName.substring(0, lastDotIndex); + const extension = fileName.substring(lastDotIndex); + + if (fileName.length <= MAX_FILE_NAME_LENGTH) { + return fileName; + } + + const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3; + + if (maxNameLength <= 0) { + return '...'; + } + + return nameWithoutExtension.substring(0, maxNameLength) + '...'; + } + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'document', + header: ({ column }) => ( +
+ + {t('document')} + + +
+ ), + cell: ({ row }) => ( +
+ {row.original.document} +
+ ), + }, + { + accessorKey: 'view_date', + header: ({ column }) => ( +
+ + Дата / время + + +
+ ), + cell: ({ row }) => { + return ( +
+ {row.original.view_date ? ( + <> + {formatDate(row.original.view_date)} +
+ {formatDateTime(row.original.view_date)} +
+ + ) : ( +
-
+ )} +
+ ) + }, + enableColumnFilter: false, + }, + { + accessorKey: 'viewer', + header: ({ column }) => ( +
+ + viewer + + +
+ ), + cell: ({ row }) => { + return ( +
+ {row.original.viewer} +
+ ) + }, + }, + { + accessorKey: 'location', + header: ({ column }) => ( +
+ + Местоположение + + +
+ ), + cell: ({ row }) => { + return ( +
+ {row.original.city} {row.original.country} +
+ ) + }, + } + ], + [] + ); + + // Создание таблицы + const table = useReactTable({ + data: tableData, + columns, + state: { + sorting, + pagination + }, + pageCount: totalPages, + manualPagination: true, + manualSorting: true, + manualFiltering: true, + onPaginationChange: setPagination, + onSortingChange: (updater) => { + const newSorting = typeof updater === 'function' ? updater(sorting) : updater; + setSorting(newSorting); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + return ( +
+
+

+ История открытий +

+ {/* Фильтры */} +
+
+
+
{t('items-per-page')}:
+ { + table.setPageSize(Number(newSize)); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }} + > + {['5', '10', '20', '50', '100'].map(pageSize => ( +
  • + {t('show')} {pageSize} +
  • + ))} +
    +
    +
    +
    + + {/* Таблица */} +
    + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + + {isLoading && ( + + + + )} + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + )) + ) : ( + + {!isLoading && ( + + )} + + )} + +
    + {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} +
    + {t('no-data-for-selected-filters')} +
    +
    + + {/* Пагинация */} +
    +
    + + {t('page')}{' '} + + {pagination.pageIndex + 1} {t('out-of')} {totalPages || 1} + + + + | {t('shown')} {tableData.length} {t('out-of')} {totalItems} + +
    + +
    + + + +
    + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + let pageIndex; + if (totalPages <= 5) { + pageIndex = i; + } else if (pagination.pageIndex <= 2) { + pageIndex = i; + } else if (pagination.pageIndex >= totalPages - 3) { + pageIndex = totalPages - 5 + i; + } else { + pageIndex = pagination.pageIndex - 2 + i; + } + + if (pageIndex >= 0 && pageIndex < totalPages) { + return ( + + ); + } + return null; + })} +
    + + + +
    +
    +
    +
    + ) +} \ No newline at end of file