From aa0f9398ebdb52e68532b9ff1f0046ad934e6245 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 7 Jan 2026 13:03:50 +0700 Subject: [PATCH] update user files info --- 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 +- src/app/[locale]/pages/my-content/page.tsx | 15 +-- src/app/actions/action.ts | 33 +++++ src/app/actions/auth.ts | 2 - src/app/components/PieChartComponent.tsx | 7 +- src/app/components/upload-section-file.tsx | 1 + src/app/lib/convertBytes.ts | 15 +++ src/app/lib/prefetch-queries.ts | 6 +- src/app/ui/dashboard/protection-overview.tsx | 72 +++++++---- src/app/ui/dashboard/stats-grid.tsx | 28 ++++- .../ui/marking-page/protection-summary.tsx | 52 ++++++-- .../ui/my-content/my-content-info-block.tsx | 114 +++++++++++++++--- .../ui/my-content/my-content-pie-chart.tsx | 95 +++++++++++++++ src/app/ui/payment/payment-tabs.tsx | 1 - 16 files changed, 371 insertions(+), 76 deletions(-) create mode 100644 src/app/lib/convertBytes.ts create mode 100644 src/app/ui/my-content/my-content-pie-chart.tsx diff --git a/src/app/[locale]/pages/marking-audio/page.tsx b/src/app/[locale]/pages/marking-audio/page.tsx index 0450f29..9669682 100644 --- a/src/app/[locale]/pages/marking-audio/page.tsx +++ b/src/app/[locale]/pages/marking-audio/page.tsx @@ -46,7 +46,7 @@ export default async function Page() {
- +
diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-photo/page.tsx index 9dfbede..9c7c39a 100644 --- a/src/app/[locale]/pages/marking-photo/page.tsx +++ b/src/app/[locale]/pages/marking-photo/page.tsx @@ -41,7 +41,7 @@ export default async function Page() {
- +
diff --git a/src/app/[locale]/pages/marking-video/page.tsx b/src/app/[locale]/pages/marking-video/page.tsx index 8063841..9f7b121 100644 --- a/src/app/[locale]/pages/marking-video/page.tsx +++ b/src/app/[locale]/pages/marking-video/page.tsx @@ -46,7 +46,7 @@ export default async function Page() {
- +
diff --git a/src/app/[locale]/pages/my-content/page.tsx b/src/app/[locale]/pages/my-content/page.tsx index 88616b8..fbbab98 100644 --- a/src/app/[locale]/pages/my-content/page.tsx +++ b/src/app/[locale]/pages/my-content/page.tsx @@ -1,24 +1,15 @@ import MyContentInfoBlock from '@/app/ui/my-content/my-content-info-block'; -import { PieChartComponent } from '@/app/components/PieChartComponent'; import FilesTable from '@/app/ui/dashboard/files-table'; - -const data = [ - { name: 'images', value: 33, color: '#f08c00' }, - { name: 'videos', value: 50, color: '#2f9e44' }, - { name: 'audios', value: 25, color: '#1971c2' }, - { name: 'documents', value: 25, color: '#a561e6' }, -]; +import MyContentPieChart from '@/app/ui/my-content/my-content-pie-chart'; export default function Page() { return ( <>
-
- -
+
- + ) } \ No newline at end of file diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index b6d6204..202f6d0 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -28,4 +28,37 @@ export async function getUserData() { } catch (error) { console.error('error'); } +} + +export async function getUserFilesInfo() { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 20005, + message_body: { + action: "user_files_info", + token: token + } + }), + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }); + + if (response.ok) { + const parsed = await response.json(); + if (parsed.message_code === 0) { + return parsed.message_body; + } else { + throw parsed.message_code; + } + } + } catch (error) { + console.error(`error: ${error}`); + } } \ No newline at end of file diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 2b838b7..5a2bd43 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -69,8 +69,6 @@ export async function authorization( }; } - console.log(`${API_BASE_URL}/api/v1/data`); - try { const { email, password } = validatedFields.data; const response = await fetch(`${API_BASE_URL}/api/v1/data`, { diff --git a/src/app/components/PieChartComponent.tsx b/src/app/components/PieChartComponent.tsx index 022e078..6e60708 100644 --- a/src/app/components/PieChartComponent.tsx +++ b/src/app/components/PieChartComponent.tsx @@ -3,12 +3,13 @@ import { useTranslations } from 'next-intl'; import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'; -export const PieChartComponent = ({ data }: { +export const PieChartComponent = ({ data, show }: { data: { name: string, value: number, color: string - }[] + }[], + show: number | string }) => { const t = useTranslations('Global'); @@ -19,7 +20,7 @@ export const PieChartComponent = ({ data }: { - 0 + {show} {t('out-of')} diff --git a/src/app/components/upload-section-file.tsx b/src/app/components/upload-section-file.tsx index 9b57e92..6243c62 100644 --- a/src/app/components/upload-section-file.tsx +++ b/src/app/components/upload-section-file.tsx @@ -294,6 +294,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile if (chunkStatus.message_body.missing_chunks === 0) { setIsFileUploaded(true); await queryClient.invalidateQueries({ queryKey: ['userFilesData'] }); + await queryClient.invalidateQueries({ queryKey: ['userFilesInfo'] }); } else { throw new Error('Not all chunks were uploaded'); } diff --git a/src/app/lib/convertBytes.ts b/src/app/lib/convertBytes.ts new file mode 100644 index 0000000..3516c98 --- /dev/null +++ b/src/app/lib/convertBytes.ts @@ -0,0 +1,15 @@ +export function convertBytes(bytes: number) { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + + if (bytes === 0) return '0 B'; + + let i = 0; + let value = bytes; + + while (value >= 1024 && i < units.length - 1) { + value /= 1024; + i++; + } + + return `${value.toFixed(1)} ${units[i]}`; +} diff --git a/src/app/lib/prefetch-queries.ts b/src/app/lib/prefetch-queries.ts index 97636f3..f2c766d 100644 --- a/src/app/lib/prefetch-queries.ts +++ b/src/app/lib/prefetch-queries.ts @@ -1,5 +1,5 @@ import { QueryClient } from '@tanstack/react-query'; -import { getUserData } from '@/app/actions/action'; +import { getUserData, getUserFilesInfo } from '@/app/actions/action'; import { getUserFilesData } from '@/app/actions/fileEntity'; export async function prefetchLayoutQueries(queryClient: QueryClient) { @@ -16,5 +16,9 @@ export async function prefetchLayoutQueries(queryClient: QueryClient) { }, staleTime: 30 * 1000 }), + queryClient.prefetchQuery({ + queryKey: ['userFilesInfo'], + queryFn: () => getUserFilesInfo() + }), ]); } \ No newline at end of file diff --git a/src/app/ui/dashboard/protection-overview.tsx b/src/app/ui/dashboard/protection-overview.tsx index d566a7e..f8cc8e3 100644 --- a/src/app/ui/dashboard/protection-overview.tsx +++ b/src/app/ui/dashboard/protection-overview.tsx @@ -1,29 +1,30 @@ 'use client' -import { useState, useRef } from 'react'; +import { useState, useRef, useEffect } from 'react'; import { PieChartComponent } from '@/app/components/PieChartComponent'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import { useQuery } from '@tanstack/react-query'; -import { getUserFilesData } from '@/app/actions/fileEntity'; - - -const data = [ - { name: 'images', value: 33, color: '#f08c00' }, - { name: 'videos', value: 50, color: '#2f9e44' }, - { name: 'audios', value: 25, color: '#1971c2' }, -]; - +import { getUserFilesInfo } from '@/app/actions/action'; +import { convertBytes } from '@/app/lib/convertBytes'; type FilesInfo = { - totalSize: string; + totalSize: number; totalCount: number; + imageQuantity: number; + videoQuantity: number; + audioQuantity: number; }; export default function ProtectionOverview() { const [isOpen, setIsOpen] = useState(false); + const [filesCount, setFilesCount] = useState([ + { name: 'images', value: 0, color: '#f08c00' }, + { name: 'videos', value: 0, color: '#2f9e44' }, + { name: 'audios', value: 0, color: '#1971c2' }, + ]); const t = useTranslations('Global'); const [openDropDownList, setOpenDropDownList] = useState(false); const dropDownList = useRef(null); @@ -37,26 +38,55 @@ export default function ProtectionOverview() { isError, error, } = useQuery<{ - formattedTotalSize: string, - totalCount: number + all_files_size: number, + all_files_quantity: number, + images_quantity: number, + videos_quantity: number, + audios_quantity: number }, Error, FilesInfo>({ - queryKey: ['userFilesData'], - queryFn: getUserFilesData, + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, select: (data): FilesInfo => { if (!data) { return { - totalSize: '0', - totalCount: 0 + totalSize: 0, + totalCount: 0, + imageQuantity: 0, + videoQuantity: 0, + audioQuantity: 0, } } return { - totalSize: data.formattedTotalSize, - totalCount: (data.totalCount) + totalSize: data.all_files_size, + totalCount: data.all_files_quantity, + imageQuantity: data.images_quantity, + videoQuantity: data.videos_quantity, + audioQuantity: data.audios_quantity, } }, }); + useEffect(() => { + setFilesCount([ + { + name: 'images', + value: filesInfo?.imageQuantity ? filesInfo?.imageQuantity : 0, + color: '#f08c00' + }, + { + name: 'videos', + value: filesInfo?.videoQuantity ? filesInfo?.videoQuantity : 0, + color: '#2f9e44' + }, + { + name: 'audios', + value: filesInfo?.audioQuantity ? filesInfo?.audioQuantity : 0, + color: '#1971c2' + }, + ]) + }, [filesInfo]); + return (

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

@@ -115,11 +145,11 @@ export default function ProtectionOverview() { )} {isOpen ? (
- +
) : (
-
{filesInfo?.totalSize ? filesInfo?.totalSize : 0} / 0
+
{filesInfo?.totalSize ? convertBytes(filesInfo?.totalSize) : 0} / 0
{t('disk-space-used')}
diff --git a/src/app/ui/dashboard/stats-grid.tsx b/src/app/ui/dashboard/stats-grid.tsx index 18bbd7c..9e2bbdb 100644 --- a/src/app/ui/dashboard/stats-grid.tsx +++ b/src/app/ui/dashboard/stats-grid.tsx @@ -1,8 +1,24 @@ +'use client' + import { useTranslations } from 'next-intl'; import { IconImageFile, IconVideoFile, IconAudioFile } from '@/app/ui/icons/icons'; +import { useQuery } from '@tanstack/react-query'; +import { getUserFilesInfo } from '@/app/actions/action'; +import {convertBytes} from '@/app/lib/convertBytes'; + export default function StatsGrid() { const t = useTranslations("Global"); + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + }); + return (

@@ -30,10 +46,10 @@ export default function StatsGrid() { {t('images')}

- 0 + {filesInfo.images_quantity ? filesInfo.images_quantity : 0}
- 0 + {filesInfo.images_size ? convertBytes(filesInfo.images_size) : 0}
0 @@ -47,10 +63,10 @@ export default function StatsGrid() { {t('videos')}
- 0 + {filesInfo.videos_quantity ? filesInfo.videos_quantity : 0}
- 0 + {filesInfo.videos_size ? convertBytes(filesInfo.videos_size) : 0}
0 @@ -64,10 +80,10 @@ export default function StatsGrid() { {t('audios')}
- 0 + {filesInfo.audios_quantity ? filesInfo.audios_quantity : 0}
- 0 + {filesInfo.audios_size ? convertBytes(filesInfo.audios_size) : 0}
0 diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index f07d0c4..7c32df8 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -3,15 +3,43 @@ import { useTranslations } from 'next-intl'; import { useQuery } from '@tanstack/react-query'; import { getUserFilesData } from '@/app/actions/fileEntity'; +import { getUserFilesInfo } from '@/app/actions/action'; +import { convertBytes } from '@/app/lib/convertBytes'; type FilesInfo = { - totalSize: string; + totalSize: number; totalCount: number; }; -export default function ProtectionSummary() { +export default function ProtectionSummary({ fileType }: { fileType: string }) { const t = useTranslations('Global'); + console.log(fileType); + + const getFileTypeFields = (fileType: string): { totalSize: string, totalCount: string } => { + switch (fileType) { + case 'image': + return { + totalSize: 'images_size', + totalCount: 'images_quantity' + }; + case 'video': + return { + totalSize: 'videos_size', + totalCount: 'videos_quantity' + }; + case 'audio': + return { + totalSize: 'audios_size', + totalCount: 'audios_quantity' + }; + default: + return { + totalSize: 'all_files_size', + totalCount: 'all_files_quantity' + }; + } + }; const { data: filesInfo, @@ -19,24 +47,26 @@ export default function ProtectionSummary() { isError, error, } = useQuery<{ - formattedTotalSize: string, - totalCount: number + all_files_size: number, + all_files_quantity: number }, Error, FilesInfo>({ - queryKey: ['userFilesData'], - queryFn: getUserFilesData, + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, select: (data): FilesInfo => { if (!data) { return { - totalSize: '0', + totalSize: 0, totalCount: 0 } } + const fields = getFileTypeFields(fileType); + return { - totalSize: data.formattedTotalSize, - totalCount: (data.totalCount) + totalSize: data[fields.totalSize as keyof typeof data], + totalCount: data[fields.totalCount as keyof typeof data] } - }, + } }); @@ -56,7 +86,7 @@ export default function ProtectionSummary() {
{t('violations')}
-
{filesInfo?.totalSize ? filesInfo?.totalSize : 0} / 0
+
{filesInfo?.totalSize ? convertBytes(filesInfo?.totalSize) : 0} / 0
{t('disk-space-used')}
diff --git a/src/app/ui/my-content/my-content-info-block.tsx b/src/app/ui/my-content/my-content-info-block.tsx index b8cb607..c3d5eb6 100644 --- a/src/app/ui/my-content/my-content-info-block.tsx +++ b/src/app/ui/my-content/my-content-info-block.tsx @@ -1,6 +1,72 @@ +'use client' + import { IconDocument, IconImageFile, IconVideoFile, IconAudioFile } from '@/app/ui/icons/icons'; +import { useQuery } from '@tanstack/react-query'; +import { getUserFilesInfo } from '@/app/actions/action'; +import { useTranslations } from 'next-intl'; +import { convertBytes } from '@/app/lib/convertBytes'; export default function MyContentInfoBlock() { + const t = useTranslations('Global'); + + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + }); + + const fileTypeSizePercents = () => { + if (!filesInfo?.all_files_size) { + return { + images: 0, + videos: 0, + audios: 0, + documents: 0, + }; + } + + const sizes = { + images: filesInfo.images_size || 0, + videos: filesInfo.videos_size || 0, + audios: filesInfo.audios_size || 0, + documents: filesInfo.documents_size || 0, + }; + + const total = filesInfo.all_files_size; + + const rawPercents = { + images: (sizes.images / total) * 100, + videos: (sizes.videos / total) * 100, + audios: (sizes.audios / total) * 100, + documents: (sizes.documents / total) * 100, + }; + + const roundedPercents = { + images: Math.floor(rawPercents.images), + videos: Math.floor(rawPercents.videos), + audios: Math.floor(rawPercents.audios), + documents: Math.floor(rawPercents.documents), + }; + + const remainder = 100 - Object.values(roundedPercents).reduce((a, b) => a + b, 0); + + const fractions = Object.entries(rawPercents).map(([key, value]) => ({ + key, + fraction: value - Math.floor(value) + })).sort((a, b) => b.fraction - a.fraction); + + const result = { ...roundedPercents }; + for (let i = 0; i < remainder; i++) { + result[fractions[i].key as keyof typeof result]++; + } + + return result; + }; + return (
@@ -11,13 +77,17 @@ export default function MyContentInfoBlock() {
-

Image

- 0% +

{t('images')}

+ {fileTypeSizePercents().images}%
- 0 files - 0 GB + + {filesInfo.images_quantity ? filesInfo.images_quantity : 0} {t('files')} + + + {filesInfo.images_size ? convertBytes(filesInfo.images_size) : 0} +
@@ -27,13 +97,17 @@ export default function MyContentInfoBlock() {
-

Videos

- 0% +

{t('videos')}

+ {fileTypeSizePercents().videos}%
- 0 files - 0 GB + + {filesInfo.videos_quantity ? filesInfo.videos_quantity : 0} {t('files')} + + + {filesInfo.videos_size ? convertBytes(filesInfo.videos_size) : 0} +
@@ -43,13 +117,17 @@ export default function MyContentInfoBlock() {
-

Audios

- 0% +

{t('audios')}

+ {fileTypeSizePercents().audios}%
- 0 files - 0 GB + + {filesInfo.audios_quantity ? filesInfo.audios_quantity : 0} {t('files')} + + + {filesInfo.audios_size ? convertBytes(filesInfo.audios_size) : 0} +
@@ -59,13 +137,17 @@ export default function MyContentInfoBlock() {
-

Documents

- 0% +

{t('documents')}

+ {fileTypeSizePercents().documents}%
- 0 files - 0 GB + + {filesInfo.documents_quantity ? filesInfo.documents_quantity : 0} {t('files')} + + + {filesInfo.documents_size ? convertBytes(filesInfo.documents_size) : 0} +
diff --git a/src/app/ui/my-content/my-content-pie-chart.tsx b/src/app/ui/my-content/my-content-pie-chart.tsx new file mode 100644 index 0000000..03b6ddf --- /dev/null +++ b/src/app/ui/my-content/my-content-pie-chart.tsx @@ -0,0 +1,95 @@ +'use client' + +import { PieChartComponent } from '@/app/components/PieChartComponent'; +import { useQuery } from '@tanstack/react-query'; +import { getUserFilesInfo } from '@/app/actions/action'; +import { useState, useEffect } from 'react'; +import {convertBytes} from '@/app/lib/convertBytes'; + + +type FilesInfo = { + totalSize: number; + totalCount: number; + imageQuantity: number; + videoQuantity: number; + audioQuantity: number; + documentQuantity: number; +}; + +export default function MyContentPieChart() { + const [filesCount, setFilesCount] = useState([ + { name: 'images', value: 0, color: '#f08c00' }, + { name: 'videos', value: 0, color: '#2f9e44' }, + { name: 'audios', value: 0, color: '#1971c2' }, + { name: 'documents', value: 0, color: '#a561e6' }, + ]); + + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery<{ + all_files_size: number, + all_files_quantity: number, + images_quantity: number, + videos_quantity: number, + audios_quantity: number, + documents_quantity: number, + }, Error, FilesInfo>({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + select: (data): FilesInfo => { + if (!data) { + return { + totalSize: 0, + totalCount: 0, + imageQuantity: 0, + videoQuantity: 0, + audioQuantity: 0, + documentQuantity: 0 + } + } + + return { + totalSize: data.all_files_size, + totalCount: data.all_files_quantity, + imageQuantity: data.images_quantity, + videoQuantity: data.videos_quantity, + audioQuantity: data.audios_quantity, + documentQuantity: data.documents_quantity, + } + }, + }); + + useEffect(() => { + setFilesCount([ + { + name: 'images', + value: filesInfo?.imageQuantity ? filesInfo?.imageQuantity : 0, + color: '#f08c00' + }, + { + name: 'videos', + value: filesInfo?.videoQuantity ? filesInfo?.videoQuantity : 0, + color: '#2f9e44' + }, + { + name: 'audios', + value: filesInfo?.audioQuantity ? filesInfo?.audioQuantity : 0, + color: '#1971c2' + }, + { + name: 'documents', + value: filesInfo?.documentQuantity ? filesInfo?.documentQuantity : 0, + color: '#a561e6' + }, + ]) + }, [filesInfo]); + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/src/app/ui/payment/payment-tabs.tsx b/src/app/ui/payment/payment-tabs.tsx index 2edf12e..1d7c435 100644 --- a/src/app/ui/payment/payment-tabs.tsx +++ b/src/app/ui/payment/payment-tabs.tsx @@ -9,7 +9,6 @@ import PaymentTabTransactionHistory from './payment-tab-transaction-history'; export default function PaymentTabs() { let [selectedTab, setSelectedTab] = useState('1'); function switchTabsHandler(tab: string) { - console.log('click'); setSelectedTab(tab); } return (