TorbatYar/frontend/app/admin/page.tsx
Mortezakoohjani 0d424c500a feat(platform): complete commercial runtime consolidation
Unify commercial runtime ownership across backend and frontend so platform, experience, and hospitality modules use the shared commercial source of truth.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 11:11:53 +03:30

109 lines
4.3 KiB
TypeScript
Raw Permalink 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، سیاست، پیشنهاد — SoT" },
{ href: "/admin/tenants", title: "مدیریت Tenantها", desc: "ایجاد، ویرایش، تعلیق و فعال‌سازی" },
{ href: "/admin/users", title: "مدیریت کاربران", desc: "لیست و ایجاد کاربر جدید" },
{ href: "/admin/commercial/plans", title: "پلن‌های تجاری", desc: "Commercial Runtime plans registry" },
{ href: "/admin/commercial/capabilities", title: "قابلیت‌ها", desc: "Commercial Runtime capabilities" },
{ 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, services, users] = await Promise.allSettled([
api.platformTenants.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: null,
features: 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/commercial/plans" },
{ label: "قابلیت‌ها", value: stats.features, href: "/admin/commercial/capabilities" },
{ 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>
);
}