diff --git a/src/views/AdminDashboard.tsx b/src/views/AdminDashboard.tsx
new file mode 100644
index 0000000..6a8b557
--- /dev/null
+++ b/src/views/AdminDashboard.tsx
@@ -0,0 +1,592 @@
+"use client";
+
+import * as React from "react";
+import { useQuery } from "@tanstack/react-query";
+import {
+ Activity,
+ BarChart3,
+ BookOpen,
+ CalendarDays,
+ Heart,
+ Landmark,
+ LineChart as LineChartIcon,
+ Save,
+ TrendingUp,
+ UsersRound,
+ WalletCards,
+} from "lucide-react";
+import {
+ Bar,
+ BarChart,
+ CartesianGrid,
+ Cell,
+ Line,
+ LineChart,
+ Scatter,
+ ScatterChart,
+ XAxis,
+ YAxis,
+ ZAxis,
+} from "recharts";
+import { api } from "@/lib/api";
+import type {
+ AdminDashboardAnalyticsSchema,
+ AnalyticsPointSchema,
+ AnalyticsPostPopularitySchema,
+ AnalyticsTrendPointSchema,
+ EventListItemSchema,
+} from "@/lib/types";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { formatNumberPersian, formatToman, resolveErrorMessage, toPersianDigits } from "@/lib/utils";
+
+const chartColors = [
+ "hsl(var(--primary))",
+ "hsl(var(--chart-2, 173 58% 39%))",
+ "hsl(var(--chart-3, 197 37% 24%))",
+ "hsl(var(--chart-4, 43 74% 66%))",
+ "hsl(var(--chart-5, 27 87% 67%))",
+];
+
+type DashboardFilters = {
+ date_from: string;
+ date_to: string;
+ event_id: string;
+ granularity: "auto" | "day" | "week" | "month";
+};
+
+function StatCard({
+ title,
+ value,
+ description,
+ icon: Icon,
+}: {
+ title: string;
+ value: string;
+ description: string;
+ icon: React.ComponentType<{ className?: string }>;
+}) {
+ return (
+
+
+
+
{title}
+
{value}
+
{description}
+
+
+
+
+
+
+ );
+}
+
+function EmptyChart({ label = "دادهای برای نمایش وجود ندارد." }: { label?: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
+function VerticalBarChart({ data, color = "var(--color-value)" }: { data: AnalyticsPointSchema[]; color?: string }) {
+ if (!data.length) return ;
+ return (
+
+
+
+ formatNumberPersian(Number(value))} />
+
+ } />
+
+
+
+ );
+}
+
+function TrendLineChart({ data, color = "var(--color-value)" }: { data: AnalyticsTrendPointSchema[]; color?: string }) {
+ if (!data.length) return ;
+ return (
+
+
+
+
+ formatNumberPersian(Number(value))} />
+ } />
+
+
+
+ );
+}
+
+function StatusBarChart({ data }: { data: AdminDashboardAnalyticsSchema["events"]["registration_status"] }) {
+ if (!data.length) return ;
+ return (
+
+
+
+
+ formatNumberPersian(Number(value))} />
+ } />
+
+ {data.map((_, index) => (
+ |
+ ))}
+
+
+
+ );
+}
+
+function BlogScatterChart({ data }: { data: AnalyticsPostPopularitySchema[] }) {
+ if (!data.length) return ;
+ return (
+
+
+
+ formatNumberPersian(Number(value))}
+ />
+ formatNumberPersian(Number(value))}
+ />
+
+ {
+ if (!active || !payload?.length) return null;
+ const item = payload[0].payload as AnalyticsPostPopularitySchema;
+ return (
+
+
{item.title}
+
+
لایک: {formatNumberPersian(item.likes)}
+
ذخیره: {formatNumberPersian(item.saves)}
+
کامنت: {formatNumberPersian(item.comments)}
+
+
+ );
+ }}
+ />
+
+
+
+ );
+}
+
+function TopList({
+ title,
+ items,
+ renderMeta,
+}: {
+ title: string;
+ items: Array<{ id: number; title: string }>;
+ renderMeta: (item: { id: number; title: string }) => React.ReactNode;
+}) {
+ return (
+
+
+ {title}
+
+
+ {items.length ? (
+ items.map((item, index) => (
+
+
+
{item.title}
+
{renderMeta(item)}
+
+
{toPersianDigits(index + 1)}
+
+ ))
+ ) : (
+ دادهای وجود ندارد.
+ )}
+
+
+ );
+}
+
+function ActivityTrendChart({ data }: { data: AdminDashboardAnalyticsSchema["blog"]["activity_trend"] }) {
+ if (!data.length) return ;
+ return (
+
+
+
+
+ formatNumberPersian(Number(value))} />
+ } />
+
+
+
+
+
+ );
+}
+
+export default function AdminDashboard() {
+ const [filters, setFilters] = React.useState({
+ date_from: "",
+ date_to: "",
+ event_id: "all",
+ granularity: "auto",
+ });
+
+ const eventsQuery = useQuery({
+ queryKey: ["admin", "dashboard", "events"],
+ queryFn: () => api.getEvents({ limit: 100 }),
+ });
+
+ const dashboardQuery = useQuery({
+ queryKey: ["admin", "dashboard", filters],
+ queryFn: () =>
+ api.getAdminDashboard({
+ date_from: filters.date_from || undefined,
+ date_to: filters.date_to || undefined,
+ event_id: filters.event_id === "all" ? undefined : Number(filters.event_id),
+ granularity: filters.granularity,
+ }),
+ });
+
+ const dashboard = dashboardQuery.data;
+ const selectedEvent = React.useMemo(() => {
+ if (filters.event_id === "all") return null;
+ return (eventsQuery.data ?? []).find((event) => String(event.id) === filters.event_id) ?? null;
+ }, [eventsQuery.data, filters.event_id]);
+
+ const setFilter = (key: K, value: DashboardFilters[K]) => {
+ setFilters((current) => ({ ...current, [key]: value }));
+ };
+
+ const resetFilters = () => {
+ setFilters({ date_from: "", date_to: "", event_id: "all", granularity: "auto" });
+ };
+
+ return (
+
+
+
+
داشبورد دستاوردها
+
+ نمای کلی از رشد کاربران، تنوع رویدادها، درآمد و تعاملات بلاگ انجمن
+
+
+ {selectedEvent ?
فیلتر رویداد: {selectedEvent.title} : null}
+
+
+
+
+
+
+ فیلتر گزارش
+
+ بازه زمانی روی هر بخش با تاریخ مناسب همان بخش اعمال میشود.
+
+
+
+
+ از تاریخ
+ setFilter("date_from", event.target.value)}
+ />
+
+
+ تا تاریخ
+ setFilter("date_to", event.target.value)}
+ />
+
+
+ رویداد
+ setFilter("event_id", value)}>
+
+
+
+
+ همه رویدادها
+ {(eventsQuery.data ?? []).map((event: EventListItemSchema) => (
+
+ {event.title}
+
+ ))}
+
+
+
+
+ دقت زمانی
+ setFilter("granularity", value as DashboardFilters["granularity"])}>
+
+
+
+
+ خودکار
+ روزانه
+ هفتگی
+ ماهانه
+
+
+
+
+
+ پاککردن
+
+
+
+
+
+
+ {dashboardQuery.isLoading ? (
+
+ در حال بارگذاری دادههای داشبورد...
+
+ ) : dashboardQuery.isError ? (
+
+
+ {resolveErrorMessage(dashboardQuery.error)}
+
+
+ ) : dashboard ? (
+
+ ) : null}
+
+ );
+}
+
+function DashboardContent({ dashboard }: { dashboard: AdminDashboardAnalyticsSchema }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ترکیب کاربران بر اساس رشته
+ کاربران ثبتنامشده در بازه انتخابی
+
+
+
+
+
+
+
+ ترکیب کاربران بر اساس دانشگاه
+ برای سنجش گستره جذب انجمن
+
+
+
+
+
+
+
+
+
+
+
+
+ روند درآمد موفق
+
+ فقط پرداختهای تاییدشده درگاه
+
+
+
+
+
+
+
+ وضعیت ثبتنامها
+ همه وضعیتها در بازه/رویداد انتخابی
+
+
+
+
+
+
+
+
+
+
+ تنوع رشته در رویدادها
+ ثبتنامهای تاییدشده و حاضرشده
+
+
+
+
+
+
+
+ تنوع دانشگاه در رویدادها
+ قابل فیلتر برای هر رویداد
+
+
+
+
+
+
+
+
+
+
+ محبوبیت نوشتهها
+ نمودار نقطهای لایک در برابر ذخیره؛ اندازه نقطه بر اساس کامنت
+
+
+
+
+
+
+
+ روند تعاملات بلاگ
+ لایک، ذخیره و کامنت در زمان
+
+
+
+
+
+
+
+
+
{
+ const event = dashboard.events.top_events.find((candidate) => candidate.id === item.id);
+ if (!event) return null;
+ return (
+
+ {formatNumberPersian(event.attendees)} نفر
+ {event.fill_rate != null ? ` · ${formatNumberPersian(event.fill_rate)}٪ ظرفیت` : ""}
+ {event.revenue ? ` · ${formatToman(event.revenue)}` : ""}
+
+ );
+ }}
+ />
+ {
+ const post = dashboard.blog.top_posts.find((candidate) => candidate.id === item.id);
+ if (!post) return null;
+ return (
+
+ {formatNumberPersian(post.likes)} لایک · {formatNumberPersian(post.saves)} ذخیره ·{" "}
+ {formatNumberPersian(post.comments)} کامنت
+
+ );
+ }}
+ />
+
+
+ موضوعات فعال بلاگ
+ دستهبندیها و تگهای دارای نوشته منتشرشده
+
+
+
+
+
+
+
+
+
+
+
+
+ روند رشد کاربران و ثبتنام رویدادها
+
+
+
+
+
+
+
+ >
+ );
+}