update general statistic and analytic

This commit is contained in:
smanylov
2026-03-19 12:49:46 +07:00
parent 07eb00c705
commit f2428d7fde
14 changed files with 210 additions and 94 deletions
+3 -1
View File
@@ -5,6 +5,7 @@ import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
import ActivityByContentType from '@/app/ui/reports/activity-by-content-type'; import ActivityByContentType from '@/app/ui/reports/activity-by-content-type';
import PageTitle from '@/app/ui/page-title'; import PageTitle from '@/app/ui/page-title';
import ReportsInfo from '@/app/ui/reports/reports-info'; import ReportsInfo from '@/app/ui/reports/reports-info';
import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
export default function Page() { export default function Page() {
return ( return (
@@ -13,7 +14,8 @@ export default function Page() {
<ReportsInfo /> <ReportsInfo />
<ActivityByContentType /> <ActivityByContentType />
<div className="analytics-grid"> <div className="analytics-grid">
<AnalyticsCardMonth /> {/* <AnalyticsCardMonth /> */}
<AnalyticsCardDistribution />
<AnalyticsCardGeography /> <AnalyticsCardGeography />
</div> </div>
<ViolationsTable /> <ViolationsTable />
+6 -7
View File
@@ -1,13 +1,12 @@
import PageTitleColorFrame from '@/app/ui/page-title-color-frame'; import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
import DashboardPlatformsList from '@/app/ui/dashboard/new/dashboard-platforms-list';
import ViolationsMyCases from '@/app/ui/violations/violations-my-cases'; import ViolationsMyCases from '@/app/ui/violations/violations-my-cases';
import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section'; import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section';
import ViolationsTable from '@/app/ui/violations/violations-table'; import ViolationsTable from '@/app/ui/violations/violations-table';
import ViolationsStatistic from '@/app/ui/violations/violations-statistic'; import ViolationsStatistic from '@/app/ui/violations/violations-statistic';
export default function Page() { import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
const FILE_TYPE = "all"; import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
export default function Page() {
return ( return (
<> <>
<PageTitleColorFrame <PageTitleColorFrame
@@ -18,9 +17,9 @@ export default function Page() {
<ViolationsMyCases /> <ViolationsMyCases />
<ViolationsCheckAllSection /> <ViolationsCheckAllSection />
<ViolationsTable /> <ViolationsTable />
<div className="dashboard-main-grid"> <div className="analytics-grid">
<DashboardPlatformsList /> <AnalyticsCardDistribution />
<DahboardGeographySection /> <AnalyticsCardGeography />
</div> </div>
</> </>
) )
+43 -7
View File
@@ -186,11 +186,9 @@ export async function getFileViolations(fileId: string, page: number) {
} }
} }
export async function fetchViolationGeography() { export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
const token = await getSessionData('token'); const token = await getSessionData('token');
/* "group_by": "tld" */
try { try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, { const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST', method: 'POST',
@@ -198,8 +196,10 @@ export async function fetchViolationGeography() {
version: 1, version: 1,
msg_id: 30009, msg_id: 30009,
message_body: { message_body: {
group_by: "domain", group_by: group,
token: token action: "group",
token: token,
sort_direction: 'desc'
} }
}), }),
headers: { headers: {
@@ -210,8 +210,44 @@ export async function fetchViolationGeography() {
if (response.ok) { if (response.ok) {
let parsed = await response.json(); let parsed = await response.json();
if (parsed) {
return parsed; if (parsed?.message_body) {
return parsed?.message_body;
} else {
throw null;
}
} else {
throw (`${response.status}`);
}
} catch (error) {
return null
}
}
export async function fetchViolationStatistic() {
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: 30010,
message_body: {
token: token,
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (response.ok) {
let parsed = await response.json();
if (parsed?.message_body) {
return parsed?.message_body;
} else { } else {
throw null; throw null;
} }
@@ -488,9 +488,6 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
); );
const handleView = async (file: FileItem) => { const handleView = async (file: FileItem) => {
console.log(`Просмотр файла: ${file.fileName}`);
console.log(file);
const fileInfo = await viewFileInfo(file.id); const fileInfo = await viewFileInfo(file.id);
setOpenWindowChildren(() => { setOpenWindowChildren(() => {
@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import { fetchViolationAnalyticStatistic } from '@/app/actions/violationActions';
export interface UseViolationStatistic {
[key: string]: number
}
export const useViolationAnalyticStatistic = (group: 'domain' | 'tld') => {
return useQuery({
queryKey: ['violationAnalyticStatistic', group],
queryFn: () => {
return fetchViolationAnalyticStatistic(group)
},
select: (data: UseViolationStatistic | null) => {
if (!data) {
return null
}
return data;
},
retry: false
});
};
@@ -1,24 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { fetchViolationGeography } from '@/app/actions/violationActions';
export interface UseViolationGeography {
ru: number;
usa: number;
uk: number;
ge: number;
}
export const useViolationGeography = () => {
return useQuery({
queryKey: ['violationGeography'],
queryFn: fetchViolationGeography,
select: (data): any | null => {
if (!data) {
return []
}
return data;
},
retry: false
});
};
@@ -0,0 +1,25 @@
import { useQuery } from '@tanstack/react-query';
import { fetchViolationStatistic } from '@/app/actions/violationActions';
export interface UseViolationStatistic {
total_violations: 2565,
new_violations: 2565,
in_progress_violations: 0,
resolved_violations: 0
}
export const useViolationStatistic = () => {
return useQuery({
queryKey: ['violationStatistic'],
queryFn: () => {
return fetchViolationStatistic()
},
select: (data: UseViolationStatistic | null) => {
if (!data) {
return null
}
return data;
},
retry: false
});
};
@@ -0,0 +1,52 @@
'use client'
import { useTranslations } from 'next-intl'
import { useViolationAnalyticStatistic, UseViolationStatistic } from '@/app/hooks/react-query/useViolationAnalyticStatistic';
interface DomainListProps {
data: UseViolationStatistic;
}
export default function AnalyticsCardDistribution() {
const t = useTranslations('Global');
const { data: violationGeography, isLoading, isError, error } = useViolationAnalyticStatistic('domain');
const DomainList = ({ data }: DomainListProps) => {
const topDomains = Object.entries(data)
.map(([domain, count]) => ({ domain, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 6);
return (
<div className="storage-timeline">
{topDomains.map((item, index) => (
<div
className="timeline-item"
key={item.domain}
>
<span className="timeline-month">
{item.domain}
</span>
<span className="method-count">
{item.count}
</span>
</div>
))}
</div>
);
};
return (
<div className="analytics-card">
<div className="analytics-header">
<div className="analytics-icon"></div>
<h3 className="analytics-title">
{t('distribution-analytics')}
</h3>
</div>
{violationGeography && (
<DomainList data={violationGeography} />
)}
</div>
)
}
+34 -40
View File
@@ -1,11 +1,40 @@
'use client' 'use client'
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import {useViolationGeography} from '@/app/hooks/react-query/useViolationGeography'; import {useViolationAnalyticStatistic, UseViolationStatistic} from '@/app/hooks/react-query/useViolationAnalyticStatistic';
interface DomainListProps {
data: UseViolationStatistic;
}
export default function AnalyticsCardGeography() { export default function AnalyticsCardGeography() {
const t = useTranslations('Global'); const t = useTranslations('Global');
const { data: violationGeography, isLoading, isError, error } = useViolationGeography(); const { data: violationGeography, isLoading, isError, error } = useViolationAnalyticStatistic('tld');
const DomainList = ({ data }: DomainListProps) => {
const topDomains = Object.entries(data)
.map(([domain, count]) => ({ domain, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 6);
return (
<div className="storage-timeline">
{topDomains.map((item, index) => (
<div
className="timeline-item"
key={item.domain}
>
<span className="timeline-month">
{item.domain}
</span>
<span className="method-count">
{item.count}
</span>
</div>
))}
</div>
);
};
return ( return (
<div className="analytics-card"> <div className="analytics-card">
@@ -15,44 +44,9 @@ export default function AnalyticsCardGeography() {
{t('geography-of-violations')} {t('geography-of-violations')}
</h3> </h3>
</div> </div>
<div className="storage-timeline"> {violationGeography && (
<div className="timeline-item"> <DomainList data={violationGeography} />
<span className="timeline-month"> )}
{t('russia')}
</span>
<span className="method-count">0</span>
</div>
<div className="timeline-item">
<span className="timeline-month">
{t('usa')}
</span>
<span className="method-count">0</span>
</div>
<div className="timeline-item">
<span className="timeline-month">
{t('germany')}
</span>
<span className="method-count">0</span>
</div>
<div className="timeline-item">
<span className="timeline-month">
{t('france')}
</span>
<span className="method-count">0</span>
</div>
<div className="timeline-item">
<span className="timeline-month">
{t('uk')}
</span>
<span className="method-count">0</span>
</div>
<div className="timeline-item">
<span className="timeline-month">
{t('china')}
</span>
<span className="method-count">0</span>
</div>
</div>
</div> </div>
) )
} }
+8 -2
View File
@@ -3,10 +3,12 @@
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
export default function FinalSummary() { export default function FinalSummary() {
const t = useTranslations('Global'); const t = useTranslations('Global');
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
const { data: violationStatistic } = useViolationStatistic();
return ( return (
<div className="final-summary"> <div className="final-summary">
@@ -30,13 +32,17 @@ export default function FinalSummary() {
<div className="summary-stat center"> <div className="summary-stat center">
{/* <div className="summary-stat-icon">🚨</div> */} {/* <div className="summary-stat-icon">🚨</div> */}
<div> <div>
<div className="summary-stat-value">{filesInfo?.total.violation ? filesInfo?.total.violation : 0}</div> <div className="summary-stat-value">
{violationStatistic?.new_violations ?? 0}
</div>
<div className="summary-stat-label"> <div className="summary-stat-label">
{t('violations-found')} {t('violations-found')}
</div> </div>
</div> </div>
<div> <div>
<div className="summary-stat-value">0</div> <div className="summary-stat-value">
{violationStatistic?.in_progress_violations ?? 0}
</div>
<div className="summary-stat-label"> <div className="summary-stat-label">
{t('violations-registered')} {t('violations-registered')}
</div> </div>
+3 -1
View File
@@ -3,10 +3,12 @@
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
import { IconDocument, IconSearch, IconWarning, IconGraph } from '@/app/ui/icons/icons'; import { IconDocument, IconSearch, IconWarning, IconGraph } from '@/app/ui/icons/icons';
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
export default function ReportsInfo() { export default function ReportsInfo() {
const t = useTranslations("Global"); const t = useTranslations("Global");
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
const { data: violationStatistic } = useViolationStatistic();
return ( return (
<> <>
@@ -27,7 +29,7 @@ export default function ReportsInfo() {
<IconWarning /> <IconWarning />
</div> </div>
</div> </div>
<div className="stat-value">{filesInfo?.total.violation ? filesInfo?.total.violation : 0}</div> <div className="stat-value">{violationStatistic?.total_violations ?? 0}</div>
<div className="stat-label">{t('violations-few')}</div> <div className="stat-label">{t('violations-few')}</div>
</div> </div>
@@ -1,35 +1,38 @@
'use client' 'use client'
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
export default function ViolationsStatistic() { export default function ViolationsStatistic() {
const t = useTranslations('Global'); const t = useTranslations('Global');
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); const { data: violationStatistic } = useViolationStatistic();
return ( return (
<div className="protection-statistic"> <div className="protection-statistic">
<div className="protection-statistic-stat-card"> <div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number"> <div className="protection-statistic-stat-number">
{filesInfo?.total.violation} {violationStatistic?.total_violations ?? 0}
</div> </div>
<div className="protection-statistic-stat-label">{t(`total-violations`)}</div> <div className="protection-statistic-stat-label">{t(`total-violations`)}</div>
</div> </div>
<div className="protection-statistic-stat-card"> <div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number"> <div className="protection-statistic-stat-number">
{filesInfo?.total.violation} {violationStatistic?.new_violations ?? 0}
</div> </div>
<div className="protection-statistic-stat-label">{t(`new`)}</div> <div className="protection-statistic-stat-label">{t(`new`)}</div>
</div> </div>
<div className="protection-statistic-stat-card"> <div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number"> <div className="protection-statistic-stat-number">
0 {violationStatistic?.in_progress_violations ?? 0}
</div> </div>
<div className="protection-statistic-stat-label">{t(`in-progress`)}</div> <div className="protection-statistic-stat-label">{t(`in-progress`)}</div>
</div> </div>
<div className="protection-statistic-stat-card"> <div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number">0</div> <div className="protection-statistic-stat-number">
{violationStatistic?.resolved_violations ?? 0}
</div>
<div className="protection-statistic-stat-label">{t('it-decided')}</div> <div className="protection-statistic-stat-label">{t('it-decided')}</div>
</div> </div>
</div> </div>
+2 -1
View File
@@ -341,7 +341,8 @@
"germany": "Germany", "germany": "Germany",
"france": "France", "france": "France",
"uk": "United Kingdom", "uk": "United Kingdom",
"china": "China" "china": "China",
"distribution-analytics": "Distribution analytics"
}, },
"Login-register-form": { "Login-register-form": {
"and": "and", "and": "and",
+2 -1
View File
@@ -341,7 +341,8 @@
"germany": "Германия", "germany": "Германия",
"france": "Франция", "france": "Франция",
"uk": "Великобритания", "uk": "Великобритания",
"china": "Китай" "china": "Китай",
"distribution-analytics": "Аналитика распространения"
}, },
"Login-register-form": { "Login-register-form": {
"and": "и", "and": "и",