From 5c15727516c77980abc634c0209ab643dcf0a541 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Sun, 14 Jun 2026 09:52:14 +0330 Subject: [PATCH] feat(admin): wire analytics dashboard route --- src/app/admin/dashboard/page.tsx | 5 ++ src/app/admin/page.tsx | 2 +- src/lib/api.ts | 16 +++++ src/lib/types.ts | 102 +++++++++++++++++++++++++++++++ src/views/AdminLayout.tsx | 9 +++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/app/admin/dashboard/page.tsx diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx new file mode 100644 index 0000000..8722190 --- /dev/null +++ b/src/app/admin/dashboard/page.tsx @@ -0,0 +1,5 @@ +import AdminDashboard from "@/views/AdminDashboard"; + +export default function AdminDashboardPage() { + return ; +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index a109cf1..4bc5bbf 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default function AdminPage() { - redirect("/admin/users"); + redirect("/admin/dashboard"); } diff --git a/src/lib/api.ts b/src/lib/api.ts index e2ffc30..843915d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -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( + `/api/analytics/admin/dashboard${query.toString() ? `?${query.toString()}` : ''}`, + ); + } + // ============= Blog Endpoints ============= async getPosts(params?: { diff --git a/src/lib/types.ts b/src/lib/types.ts index 54e7ccb..c3bb4ac 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -753,6 +753,108 @@ export interface PaginatedResponse { 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 export interface CreatePaymentOut { start_pay_url: string; diff --git a/src/views/AdminLayout.tsx b/src/views/AdminLayout.tsx index caccd39..706adec 100644 --- a/src/views/AdminLayout.tsx +++ b/src/views/AdminLayout.tsx @@ -9,6 +9,7 @@ import { FileText, FolderTree, GraduationCap, + LayoutDashboard, PanelRightClose, PanelRightOpen, ShieldCheck, @@ -23,6 +24,13 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component import { cn } from "@/lib/utils"; const navGroups = [ + { + key: "dashboard", + label: "داشبورد", + items: [ + { to: "/admin/dashboard", label: "داشبورد", icon: LayoutDashboard, visibility: "staff" }, + ], + }, { key: "users", label: "کاربران", @@ -59,6 +67,7 @@ export default function AdminLayout({ children }: { children: ReactNode }) { const { user, isAuthenticated, loading } = useAuth(); const [collapsed, setCollapsed] = useState(false); const [openGroups, setOpenGroups] = useState>({ + dashboard: true, users: true, events: true, blog: true,