feat(admin): wire analytics dashboard route
This commit is contained in:
5
src/app/admin/dashboard/page.tsx
Normal file
5
src/app/admin/dashboard/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import AdminDashboard from "@/views/AdminDashboard";
|
||||||
|
|
||||||
|
export default function AdminDashboardPage() {
|
||||||
|
return <AdminDashboard />;
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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?: {
|
||||||
|
|||||||
102
src/lib/types.ts
102
src/lib/types.ts
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user