diff --git a/src/app/[locale]/pages/layout.tsx b/src/app/[locale]/pages/layout.tsx index b3d5cdb..8c2bc69 100644 --- a/src/app/[locale]/pages/layout.tsx +++ b/src/app/[locale]/pages/layout.tsx @@ -31,7 +31,8 @@ export default async function RootLayout({ } const queryClient = getQueryClient(); - await prefetchLayoutQueries(queryClient); + + await prefetchLayoutQueries(queryClient); return ( diff --git a/src/app/actions/statisticActions.ts b/src/app/actions/statisticActions.ts new file mode 100644 index 0000000..c4f8f40 --- /dev/null +++ b/src/app/actions/statisticActions.ts @@ -0,0 +1,284 @@ +'use server' + +import { API_BASE_URL } from '@/app/actions/definitions'; +import { getSessionData } from '@/app/actions/session'; + +export async function fetchTariffStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30020, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchUserFilesTopStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30019, + message_body: { + top: 5 + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchUserGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30021, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchSubscribeGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30024, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchProtectedContentGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30022, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchViolationGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30023, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchTokensGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30025, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} + +export async function fetchIncomeGeneralStatistic() { + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30026, + message_body: { + active_only: true + } + }), + 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.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } +} \ No newline at end of file diff --git a/src/app/actions/usersActions.ts b/src/app/actions/usersActions.ts index e5ea692..fcd8e54 100644 --- a/src/app/actions/usersActions.ts +++ b/src/app/actions/usersActions.ts @@ -46,7 +46,6 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string, } export async function fetchUsesInfo(userInfo: number) { - console.log('fetchUsesInfo'); try { const response = await fetch(`${API_BASE_URL}/api/admin/get-users`, { diff --git a/src/app/hooks/react-query/dashboard/useIncomeGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useIncomeGeneralStatistic.ts new file mode 100644 index 0000000..785654b --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useIncomeGeneralStatistic.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchIncomeGeneralStatistic } from '@/app/actions/statisticActions'; + +interface IncomePerUser { + p5: number; + p50: number; + p95: number; +} + +interface IncomeGeneralStatistic { + total_income: number; + total_available: number; + total_hold: number; + income_per_user: IncomePerUser; +} + +export const useIncomeGeneralStatistic = () => { + return useQuery({ + queryKey: ['incomeGeneralStatistic'], + queryFn: () => { + return fetchIncomeGeneralStatistic() + }, + select: (data: IncomeGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useProtectedContentGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useProtectedContentGeneralStatistic.ts new file mode 100644 index 0000000..6cd3bca --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useProtectedContentGeneralStatistic.ts @@ -0,0 +1,41 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchProtectedContentGeneralStatistic } from '@/app/actions/statisticActions'; + +interface FilesPerUserPercentiles { + p5: number; + p50: number; + p95: number; +} + +interface SizePerUserPercentiles { + p5_bytes: number; + p50_bytes: number; + p95_bytes: number; + p5_mb: number; + p50_mb: number; + p95_mb: number; +} + +interface ProtectedContentGeneralStatistic { + total_files: number; + total_size_bytes: number; + total_size_gb: number; + files_per_user_percentiles: FilesPerUserPercentiles; + size_per_user_percentiles: SizePerUserPercentiles; +} + +export const useProtectedContentGeneralStatistic = () => { + return useQuery({ + queryKey: ['protectedContentGeneralStatistic'], + queryFn: () => { + return fetchProtectedContentGeneralStatistic() + }, + select: (data: ProtectedContentGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic.ts new file mode 100644 index 0000000..c4a517c --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchSubscribeGeneralStatistic } from '@/app/actions/statisticActions'; + +interface SubscriptionStats { + current_active_subscriptions: number; + first_time_subscriptions: number; + renewals: number; + upgrades: number; + downgrades: number; +} + +interface SubscribeGeneralStatistic { + monthly: SubscriptionStats; + yearly: SubscriptionStats; +} + +export const useSubscribeGeneralStatistic = () => { + return useQuery({ + queryKey: ['subscribeGeneralStatistic'], + queryFn: () => { + return fetchSubscribeGeneralStatistic() + }, + select: (data: SubscribeGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useTariffStatistic.ts b/src/app/hooks/react-query/dashboard/useTariffStatistic.ts new file mode 100644 index 0000000..78ba1ce --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useTariffStatistic.ts @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchTariffStatistic } from '@/app/actions/statisticActions'; + +interface TariffItem { + tariff_name: string; + user_count: number; + usage_percent: number; +} + +interface TariffStatistic { + tariffs: TariffItem[]; +} + +export const useMonitoringStats = () => { + return useQuery({ + queryKey: ['tariffStatistic'], + queryFn: () => { + return fetchTariffStatistic() + }, + select: (data: TariffStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useTokensGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useTokensGeneralStatistic.ts new file mode 100644 index 0000000..c5b9a60 --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useTokensGeneralStatistic.ts @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchTokensGeneralStatistic } from '@/app/actions/statisticActions'; + +interface BoughtPerUser { + p5: number; + p50: number; + p95: number; +} + +interface SpentPerUser { + p5: number; + p50: number; + p95: number; +} + +interface TokensGeneralStatistic { + total_bought: number; + total_spent: number; + bought_per_user: BoughtPerUser; + spent_per_user: SpentPerUser; +} + +export const useTokensGeneralStatistic = () => { + return useQuery({ + queryKey: ['tokensGeneralStatistic'], + queryFn: () => { + return fetchTokensGeneralStatistic() + }, + select: (data: TokensGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useUserFilesTopStatistic.ts b/src/app/hooks/react-query/dashboard/useUserFilesTopStatistic.ts new file mode 100644 index 0000000..0f0f9a0 --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useUserFilesTopStatistic.ts @@ -0,0 +1,29 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchUserFilesTopStatistic } from '@/app/actions/statisticActions'; + +interface UserItem { + full_name: string; + user_id: number; + count: number; + files_size: number; +} + +interface UserFilesTopStatistic { + tariffs: UserItem[]; +} + +export const useUserFilesTopStatistic = () => { + return useQuery({ + queryKey: ['userFilesTopStatistic'], + queryFn: () => { + return fetchUserFilesTopStatistic() + }, + select: (data: UserFilesTopStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useUserGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useUserGeneralStatistic.ts new file mode 100644 index 0000000..daafefc --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useUserGeneralStatistic.ts @@ -0,0 +1,29 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchUserGeneralStatistic } from '@/app/actions/statisticActions'; + + +interface UserGeneralStatistic { + total_users: number; + new_last_30_days: number; + new_previous_30_days: number; + growth_percent: number; + new_current_month: number; + new_previous_month: number; + month_growth_percent: number; +} + +export const useUserGeneralStatistic = () => { + return useQuery({ + queryKey: ['userGeneralStatistic'], + queryFn: () => { + return fetchUserGeneralStatistic() + }, + select: (data: UserGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/dashboard/useViolationGeneralStatistic.ts b/src/app/hooks/react-query/dashboard/useViolationGeneralStatistic.ts new file mode 100644 index 0000000..125e7e1 --- /dev/null +++ b/src/app/hooks/react-query/dashboard/useViolationGeneralStatistic.ts @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchViolationGeneralStatistic } from '@/app/actions/statisticActions'; + +interface ViolationGeneralStatistic { + total_violations: number; + total_complaints: number; + total_law_cases: number; + complaints_without_case: number; + violations_with_complaint: number; + violations_without_complaint: number; +} + +export const useViolationGeneralStatistic = () => { + return useQuery({ + queryKey: ['violationGeneralStatistic'], + queryFn: () => { + return fetchViolationGeneralStatistic() + }, + select: (data: ViolationGeneralStatistic | null) => { + if (!data) { + return null + } + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/lib/convertBytes.ts b/src/app/lib/convertBytes.ts index b1886fe..071aa44 100644 --- a/src/app/lib/convertBytes.ts +++ b/src/app/lib/convertBytes.ts @@ -1,4 +1,4 @@ -export function convertBytes(bytes: number) { +export function convertBytes(bytes: number | undefined) { if (!bytes) { return 0; } diff --git a/src/app/lib/prefetch-queries.ts b/src/app/lib/prefetch-queries.ts index 5524ea0..52985d8 100644 --- a/src/app/lib/prefetch-queries.ts +++ b/src/app/lib/prefetch-queries.ts @@ -1,14 +1,40 @@ import { QueryClient } from '@tanstack/react-query'; +import { fetchTokensGeneralStatistic, fetchProtectedContentGeneralStatistic, fetchTariffStatistic, fetchUserFilesTopStatistic, fetchUserGeneralStatistic, fetchViolationGeneralStatistic } from '@/app/actions/statisticActions'; +import { useSubscribeGeneralStatistic } from '@/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic'; export async function prefetchLayoutQueries(queryClient: QueryClient) { await Promise.all([ queryClient.prefetchQuery({ - queryKey: ['userData'], - queryFn: () => { - return { - test: 'testFetchUserData' - } - } - }) + queryKey: ['incomeGeneralStatistic'], + queryFn: () => fetchTokensGeneralStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['protectedContentGeneralStatistic'], + queryFn: () => fetchProtectedContentGeneralStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['subscribeGeneralStatistic'], + queryFn: () => useSubscribeGeneralStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['tariffStatistic'], + queryFn: () => fetchTariffStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['tokensGeneralStatistic'], + queryFn: () => fetchTokensGeneralStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['userFilesTopStatistic'], + queryFn: () => fetchUserFilesTopStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['userGeneralStatistic'], + queryFn: () => fetchUserGeneralStatistic() + }), + queryClient.prefetchQuery({ + queryKey: ['violationGeneralStatistic'], + queryFn: () => fetchViolationGeneralStatistic() + }), ]); } \ No newline at end of file diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index f86429a..128e21b 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -478,6 +478,18 @@ gap: 20px; margin-bottom: 24px; + &.user-content { + grid-template-columns: minmax(240px, 1fr) minmax(240px, 1fr) 2fr; + } + + @media (max-width: 1540px) { + grid-template-columns: repeat(2, minmax(240px, 1fr)); + + &.user-content { + grid-template-columns: repeat(2, minmax(240px, 1fr)); + } + } + .stat-card { background: var(--surface); padding: 20px; @@ -511,24 +523,18 @@ } .stat-card-value { - width: 40px; + /* width: 40px; */ + width: auto; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; - font-size: 20px; - + font-size: 16px; + padding: 0 10px; background: #eff6ff; } - /* .stat-card-value { - font-size: 32px; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 4px; - } */ - .stat-card-label { font-size: 12px; color: var(--text-secondary); @@ -1310,6 +1316,7 @@ overflow: hidden; .status { + &-active, &-verified { color: #166534; diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-content.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-content.tsx index 953cabe..8fa3e68 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-content.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-content.tsx @@ -1,18 +1,46 @@ +'use client' + +import { useProtectedContentGeneralStatistic } from '@/app/hooks/react-query/dashboard/useProtectedContentGeneralStatistic' +import { convertBytes } from '@/app/lib/convertBytes'; +import { useEffect } from 'react'; + export default function GeneralInfoContent() { + const { data, isLoading, isError } = useProtectedContentGeneralStatistic(); + + if (isLoading) { + return ( +
+

Защищенный контент (месяц/год)

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Защищенный контент (месяц/год)

+
+
Ошибка при загрузке данных
+
+
+ ); + } return (
-

+

Защищенный контент (месяц/год)

-
+
- Всего файлов этом месяце -
0
+ {/* Всего файлов этом месяце */} + Всего файлов +
{data?.total_files || 0}
В прошлом месяце @@ -22,8 +50,9 @@ export default function GeneralInfoContent() {
- Всего занято места в этом месяце -
0
+ {/* Всего занято места в этом месяце */} + Всего занято места +
{convertBytes(data?.total_size_bytes)}
В прошлом месяце @@ -34,11 +63,19 @@ export default function GeneralInfoContent() {
Файлов на пользователя -
0
+
+ {data?.files_per_user_percentiles?.p5 || 0}/ + {data?.files_per_user_percentiles?.p50 || 0}/ + {data?.files_per_user_percentiles?.p95 || 0} +
Места на диске на пользователя -
0
+
+ {convertBytes(data?.size_per_user_percentiles?.p5_bytes)} / + {convertBytes(data?.size_per_user_percentiles?.p50_bytes)} / + {convertBytes(data?.size_per_user_percentiles?.p95_bytes)} +
diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-income.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-income.tsx index f7c77fb..d5f42ba 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-income.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-income.tsx @@ -1,44 +1,67 @@ +'use client' + +import { useIncomeGeneralStatistic } from '@/app/hooks/react-query/dashboard/useIncomeGeneralStatistic'; +import { useEffect } from 'react'; + export default function GeneralInfoIncome() { + const { data, isLoading, isError } = useIncomeGeneralStatistic(); + + if (isLoading) { + return ( +
+

Доход (день/месяц)

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Доход (день/месяц)

+
+
Ошибка при загрузке данных
+
+
+ ); + } return (
-

+

Доход (день/месяц)

- Всего заработано в этом месяце -
0
+ Всего заработано +
{data?.total_income || 0} ₽
- В прошлом месяце -
0
+ Доступно +
{data?.total_available || 0} ₽
+
+
+ Заморожено +
{data?.total_hold || 0} ₽
- Всего заработано на любого пользователя в этом месяце -
0
+ Всего заработано на пользователя +
+ {data?.income_per_user?.p5 || 0}/ + {data?.income_per_user?.p50 || 0}/ + {data?.income_per_user?.p95 || 0} +
В прошлом месяце -
0
-
-
- -
-
- Всего заработано на платного пользователя в этом месяце -
0
-
-
- В прошлом месяце -
0
+
0/0/0
diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-subscriptions.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-subscriptions.tsx index 226329d..11359c0 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-subscriptions.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-subscriptions.tsx @@ -1,66 +1,91 @@ +'use client' + +import { useSubscribeGeneralStatistic } from '@/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic'; + export default function GeneralInfoSubscriptions() { + const { data, isLoading, isError } = useSubscribeGeneralStatistic(); + + if (isLoading) { + return ( +
+

Активные подписчики (месяц/год)

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Активные подписчики (месяц/год)

+
+
Ошибка при загрузке данных
+
+
+ ); + } return (
-

- Активные подписчики (месяц/год) -

+

Активные подписчики (месяц/год)

Текущие платные подписки -
0
+
+ {(data?.monthly?.current_active_subscriptions || 0) + (data?.yearly?.current_active_subscriptions || 0)} +
оформили впервые в этом месяце -
0
+
{data?.monthly?.first_time_subscriptions || 0}
в прошлом месяце -
0
+
{data?.yearly?.first_time_subscriptions || 0}
- продлили подпискув этом месяце -
0
+ продлили подписку в этом месяце +
{data?.monthly?.renewals || 0}
в прошлом месяце -
0
+
{data?.yearly?.renewals || 0}
- сделали апгрейдв этом месяце -
0
+ сделали апгрейд в этом месяце +
{data?.monthly?.upgrades || 0}
в прошлом месяце -
0
+
{data?.yearly?.upgrades || 0}
-
+{/*
- сделали даунгрейдв этом месяце -
0
+ сделали даунгрейд в этом месяце +
{data?.monthly?.downgrades || 0}
в прошлом месяце -
0
+
{data?.yearly?.downgrades || 0}
-
+
*/}
- ) + ); } \ No newline at end of file diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-tokens.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-tokens.tsx index 8fe087d..f461890 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-tokens.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-tokens.tsx @@ -1,18 +1,45 @@ +'use client' + +import { useTokensGeneralStatistic } from '@/app/hooks/react-query/dashboard/useTokensGeneralStatistic'; +import { useEffect } from 'react'; + export default function GeneralInfoTokens() { + const { data, isLoading, isError } = useTokensGeneralStatistic(); + + if (isLoading) { + return ( +
+

Токены (день/месяц)

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Токены (день/месяц)

+
+
Ошибка при загрузке данных
+
+
+ ); + } return (
-

+

Токены (день/месяц)

- Всего купленов этом месяце -
0
+ {/* Всего куплено в этом месяце */} + Всего куплено +
{data?.total_bought || 0}
В прошлом месяце @@ -22,8 +49,9 @@ export default function GeneralInfoTokens() {
- Всего потраченов этом месяце -
0
+ {/* Всего потрачено в этом месяце */} + Всего потрачено +
{data?.total_spent || 0}
В прошлом месяце @@ -34,22 +62,30 @@ export default function GeneralInfoTokens() {
Куплено на пользователя в этом месяце -
0
+
+ {data?.bought_per_user?.p5 || 0}/ + {data?.bought_per_user?.p50 || 0}/ + {data?.bought_per_user?.p95 || 0} +
В прошлом месяце -
0
+
0/0/0
Потрачено на пользователя в этом месяце -
0
+
+ {data?.spent_per_user?.p5 || 0}/ + {data?.spent_per_user?.p50 || 0}/ + {data?.spent_per_user?.p95 || 0} +
В прошлом месяце -
0
+
0/0/0
diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-users.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-users.tsx index c3d58ea..200e47d 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-users.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-users.tsx @@ -1,51 +1,87 @@ +'use client' + +import { useUserGeneralStatistic } from '@/app/hooks/react-query/dashboard/useUserGeneralStatistic'; + export default function GeneralInfoUsers() { + const { data, isLoading, isError } = useUserGeneralStatistic(); + + if (isLoading) { + return ( +
+

Кол-во пользователей и динамика

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Кол-во пользователей и динамика

+
+
Ошибка при загрузке данных
+
+
+ ); + } + + const renderDelta = (percent: number) => { + const isPositive = percent > 0; + const isNegative = percent < 0; + const sign = isPositive ? '+' : ''; + const color = isPositive ? '#10b981' : isNegative ? '#ef4444' : '#6b7280'; + + return ( +
+ {sign}{percent}% +
+ ); + }; return (
-

- Кол-во пользователей и динамика -

+

Кол-во пользователей и динамика

Всего пользователей -
0
+
{data?.total_users || 0}
Новых за 30 дней -
0
+
{data?.new_last_30_days || 0}
Новых за предыдущие 30 дней -
0
+
{data?.new_previous_30_days || 0}
Дельта -
0
+ {renderDelta(data?.growth_percent || 0)}
Новых за текущий месяц -
0
+
{data?.new_current_month || 0}
Новых за прошлый месяц -
0
+
{data?.new_previous_month || 0}
Дельта -
0
+ {renderDelta(data?.month_growth_percent || 0)}
- ) + ); } \ No newline at end of file diff --git a/src/app/ui/dashboard/general-info.tsx/general-info-violations.tsx b/src/app/ui/dashboard/general-info.tsx/general-info-violations.tsx index a192fe8..053cc5a 100644 --- a/src/app/ui/dashboard/general-info.tsx/general-info-violations.tsx +++ b/src/app/ui/dashboard/general-info.tsx/general-info-violations.tsx @@ -1,18 +1,43 @@ +'use client' + +import { useViolationGeneralStatistic } from '@/app/hooks/react-query/dashboard/useViolationGeneralStatistic'; + export default function GeneralInfoViolations() { + const { data, isLoading, isError } = useViolationGeneralStatistic(); + + if (isLoading) { + return ( +
+

Нарушения (день/месяц)

+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+

Нарушения (день/месяц)

+
+
Ошибка при загрузке данных
+
+
+ ); + } return (
-

+

Нарушения (день/месяц)

- Всего нарушений в этом месяце -
0
+ Всего совпадений +
{data?.total_violations || 0}
В прошлом месяце @@ -22,9 +47,20 @@ export default function GeneralInfoViolations() {
- Подано жалоб в этом месяце + Всего подано жалоб +
{data?.total_complaints || 0}
+
+
+ В прошлом месяце
0
+
+ +
+
+ Всего подано претензий +
{data?.total_law_cases || 0}
+
В прошлом месяце
0
diff --git a/src/app/ui/dashboard/subscriptions-statistic.tsx b/src/app/ui/dashboard/subscriptions-statistic.tsx index 24a49a6..5c2a3f7 100644 --- a/src/app/ui/dashboard/subscriptions-statistic.tsx +++ b/src/app/ui/dashboard/subscriptions-statistic.tsx @@ -1,4 +1,38 @@ +'use client' + +import { useMonitoringStats } from '@/app/hooks/react-query/dashboard/useTariffStatistic'; + export default function SubscriptionsStatistic() { + const { data, isLoading, isError } = useMonitoringStats(); + + if (isLoading) { + return ( +
+
+

Статистика подписок

+
+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+
+

Статистика подписок

+
+
+
Ошибка при загрузке данных
+
+
+ ); + } + + const tariffs = data?.tariffs || []; + return (
@@ -14,36 +48,28 @@ export default function SubscriptionsStatistic() { - - - - СТАРТ - - - 0 - 0% - - - - - ДЕМО - - - 0 - 0% - - - - - ПРОФИ - - - 0 - 0% - + {tariffs.length > 0 ? ( + tariffs.map((tariff, index) => ( + + + + {tariff.tariff_name} + + + {tariff.user_count} + {tariff.usage_percent.toFixed(2)}% + + )) + ) : ( + + + Нет данных + + + )}
- ) + ); } \ No newline at end of file diff --git a/src/app/ui/dashboard/users-top.tsx b/src/app/ui/dashboard/users-top.tsx index 90a277f..4f5b779 100644 --- a/src/app/ui/dashboard/users-top.tsx +++ b/src/app/ui/dashboard/users-top.tsx @@ -1,4 +1,37 @@ +'use client' + +import { useUserFilesTopStatistic } from '@/app/hooks/react-query/dashboard/useUserFilesTopStatistic'; +import { convertBytes } from '@/app/lib/convertBytes'; + export default function UsersTop() { + const { data, isLoading, isError } = useUserFilesTopStatistic(); + + if (isLoading) { + return ( +
+
+

Топ пользователей

+
+
+
Загрузка...
+
+
+ ); + } + + if (isError) { + return ( +
+
+

Топ пользователей

+
+
+
Ошибка при загрузке данных
+
+
+ ); + } + return (
@@ -14,55 +47,29 @@ export default function UsersTop() { - - - -
Сидоров Станислав Андреевич
-
Info@pro-ctrl.ru
- - 0 - 0 MB - - - - -
Валерка
-
va@no-copy.ru
- - 0 - 0 MB - - - - -
Игорь Викторович
-
igor@pro-ctrl.ru
- - 0 - 0 MB - - - - -
Синицын Игорь Владимирович
-
admin@no-copy.ru
- - 0 - 0 MB - - - - -
test test
-
test@no-copy.ru
- - 0 - 0 MB - - + {Array.isArray(data) && data.length > 0 ? ( + data.map((user, index) => ( + + +
{user.full_name}
+
+ ID: {user.user_id} +
+ + {user.count} + {convertBytes(user.files_size)} + + )) + ) : ( + + + Нет данных + + + )}
- ) + ); } \ No newline at end of file