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,