feat(admin): wire analytics dashboard route

This commit is contained in:
2026-06-14 09:52:14 +03:30
parent fc94ceb9f5
commit 5c15727516
5 changed files with 133 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
import AdminDashboard from "@/views/AdminDashboard";
export default function AdminDashboardPage() {
return <AdminDashboard />;
}

View File

@@ -1,5 +1,5 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export default function AdminPage() { export default function AdminPage() {
redirect("/admin/users"); redirect("/admin/dashboard");
} }

View File

@@ -416,6 +416,22 @@ class ApiClient {
}); });
} }
async getAdminDashboard(params?: {
date_from?: string;
date_to?: string;
event_id?: number;
granularity?: 'auto' | 'day' | 'week' | 'month';
}) {
const query = new URLSearchParams();
if (params?.date_from) query.set('date_from', params.date_from);
if (params?.date_to) query.set('date_to', params.date_to);
if (params?.event_id != null) query.set('event_id', String(params.event_id));
if (params?.granularity) query.set('granularity', params.granularity);
return this.request<Types.AdminDashboardAnalyticsSchema>(
`/api/analytics/admin/dashboard${query.toString() ? `?${query.toString()}` : ''}`,
);
}
// ============= Blog Endpoints ============= // ============= Blog Endpoints =============
async getPosts(params?: { async getPosts(params?: {

View File

@@ -753,6 +753,108 @@ export interface PaginatedResponse<T> {
previous?: string; previous?: string;
} }
// Admin analytics
export interface AnalyticsPointSchema {
label: string;
value: number;
}
export interface AnalyticsTrendPointSchema {
date: string;
label: string;
value: number;
}
export interface AnalyticsRegistrationStatusSchema {
status: string;
label: string;
value: number;
}
export interface AnalyticsTopEventSchema {
id: number;
title: string;
slug: string;
attendees: number;
capacity?: number | null;
fill_rate?: number | null;
revenue: number;
}
export interface AnalyticsPostPopularitySchema {
id: number;
title: string;
slug: string;
likes: number;
saves: number;
comments: number;
}
export interface AnalyticsTopPostSchema extends AnalyticsPostPopularitySchema {
score: number;
}
export interface AdminDashboardAnalyticsSchema {
filters: {
date_from?: string | null;
date_to?: string | null;
event_id?: number | null;
granularity: 'day' | 'week' | 'month';
};
summary: {
total_users: number;
verified_users: number;
total_events: number;
total_registrations: number;
total_revenue: number;
total_discount: number;
published_posts: number;
total_likes: number;
total_saves: number;
total_comments: number;
};
users: {
signup_trend: AnalyticsTrendPointSchema[];
by_major: AnalyticsPointSchema[];
by_university: AnalyticsPointSchema[];
by_year: AnalyticsPointSchema[];
};
events: {
registration_status: AnalyticsRegistrationStatusSchema[];
by_major: AnalyticsPointSchema[];
by_university: AnalyticsPointSchema[];
top_events: AnalyticsTopEventSchema[];
registration_trend: AnalyticsTrendPointSchema[];
};
revenue: {
trend: AnalyticsTrendPointSchema[];
by_event: AnalyticsPointSchema[];
payment_status: AnalyticsRegistrationStatusSchema[];
total_paid: number;
total_discount: number;
total_base: number;
};
blog: {
totals: {
posts: number;
likes: number;
saves: number;
comments: number;
};
post_popularity: AnalyticsPostPopularitySchema[];
top_posts: AnalyticsTopPostSchema[];
activity_trend: Array<{ date: string; likes: number; saves: number; comments: number }>;
by_category: AnalyticsPointSchema[];
by_tag: AnalyticsPointSchema[];
};
achievements: {
distinct_participants: number;
learning_hours: number;
published_content: number;
community_engagement: number;
};
}
// payment // payment
export interface CreatePaymentOut { export interface CreatePaymentOut {
start_pay_url: string; start_pay_url: string;

View File

@@ -9,6 +9,7 @@ import {
FileText, FileText,
FolderTree, FolderTree,
GraduationCap, GraduationCap,
LayoutDashboard,
PanelRightClose, PanelRightClose,
PanelRightOpen, PanelRightOpen,
ShieldCheck, ShieldCheck,
@@ -23,6 +24,13 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const navGroups = [ const navGroups = [
{
key: "dashboard",
label: "داشبورد",
items: [
{ to: "/admin/dashboard", label: "داشبورد", icon: LayoutDashboard, visibility: "staff" },
],
},
{ {
key: "users", key: "users",
label: "کاربران", label: "کاربران",
@@ -59,6 +67,7 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
const { user, isAuthenticated, loading } = useAuth(); const { user, isAuthenticated, loading } = useAuth();
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({ const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({
dashboard: true,
users: true, users: true,
events: true, events: true,
blog: true, blog: true,