TorbatYar/frontend/app/admin/page.tsx
2026-07-28 20:39:10 +03:30

111 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { api } from "@/lib/api";
import { getStoredToken } from "@/lib/auth";
import { identityApi } from "@/lib/identity";
import { AdminPageHeader, Banner, cardClass } from "@/components/admin/controls";
interface Stats {
tenants: number | null;
users: number | null;
plans: number | null;
features: number | null;
services: number | null;
}
const QUICK_LINKS = [
{ href: "/admin/commercial", title: "پرتال تجاری", desc: "محصولات، باندل، قیمت، trial، سیاست، پیشنهاد" },
{ href: "/admin/tenants", title: "مدیریت Tenantها", desc: "ایجاد، ویرایش، تعلیق و فعال‌سازی" },
{ href: "/admin/users", title: "مدیریت کاربران", desc: "لیست و ایجاد کاربر جدید" },
{ href: "/admin/plans", title: "پلن‌های Core", desc: "پلن‌های legacy Core (commercial plans در پرتال تجاری)" },
{ href: "/admin/features", title: "قابلیت‌ها", desc: "تعریف قابلیت‌های سرویس‌ها" },
{ href: "/admin/services", title: "سرویس‌ها", desc: "ثبت و مدیریت وضعیت سرویس‌ها" },
];
export default function AdminOverviewPage() {
const [stats, setStats] = useState<Stats>({
tenants: null,
users: null,
plans: null,
features: null,
services: null,
});
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = getStoredToken();
(async () => {
const [tenants, plans, features, services, users] = await Promise.allSettled([
api.platformTenants.list(1, 1),
api.plans.list(1, 1),
api.features.list(1, 1),
api.services.list(1, 1),
token ? identityApi.listUsers(token, { limit: 100 }) : Promise.resolve([]),
]);
setStats({
tenants: tenants.status === "fulfilled" ? tenants.value.meta.total_items : null,
plans: plans.status === "fulfilled" ? plans.value.meta.total_items : null,
features: features.status === "fulfilled" ? features.value.meta.total_items : null,
services: services.status === "fulfilled" ? services.value.meta.total_items : null,
users: users.status === "fulfilled" ? users.value.length : null,
});
if (tenants.status === "rejected") {
setError(
tenants.reason instanceof Error ? tenants.reason.message : "خطا در بارگذاری آمار"
);
}
setLoading(false);
})();
}, []);
const cards = [
{ label: "Tenantها", value: stats.tenants, href: "/admin/tenants" },
{ label: "کاربران", value: stats.users, href: "/admin/users" },
{ label: "تجاری", value: null, href: "/admin/commercial" },
{ label: "پلن‌ها", value: stats.plans, href: "/admin/plans" },
{ label: "قابلیت‌ها", value: stats.features, href: "/admin/features" },
{ label: "سرویس‌ها", value: stats.services, href: "/admin/services" },
];
return (
<div className="space-y-8">
<AdminPageHeader
title="نمای کلی پلتفرم"
subtitle="خلاصه وضعیت و دسترسی سریع به بخش‌های مدیریتی"
/>
{error && <Banner variant="error">{error}</Banner>}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
{cards.map((c) => (
<Link
key={c.label}
href={c.href}
className={`${cardClass} transition-shadow hover:shadow-md`}
>
<p className="text-xs text-gray-500">{c.label}</p>
<p className="mt-2 text-3xl font-bold text-secondary">
{loading ? "…" : c.value ?? "—"}
</p>
</Link>
))}
</div>
<div>
<h2 className="mb-4 text-lg font-bold text-secondary">دسترسی سریع</h2>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{QUICK_LINKS.map((link) => (
<Link key={link.href} href={link.href} className={`${cardClass} transition-shadow hover:shadow-md`}>
<p className="font-semibold text-secondary">{link.title}</p>
<p className="mt-1 text-sm text-gray-500">{link.desc}</p>
</Link>
))}
</div>
</div>
</div>
);
}