TorbatYar/frontend/modules/experience/features/dashboard.tsx
Mortezakoohjani 0eec9f729b feat(experience): deploy Experience frontend FE-11.0-11.5 portal
Commit completed Torbat Pages admin frontend module, BFF proxy, routes, and validation artifacts for official platform deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 21:01:50 +03:30

142 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { useQuery } from "@tanstack/react-query";
import Link from "next/link";
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Tooltip } from "recharts";
import { experienceApi } from "@/modules/experience/services/experience-api";
import { useTenantId } from "@/hooks/useTenantId";
import { useExperienceCapabilities, useExperienceMetrics } from "@/modules/experience/hooks/useExperienceCapabilities";
import { ExperiencePageLoader, ExperiencePageError } from "@/modules/experience/pages/shared";
import { PageHeader, Card, CardContent, Button, Badge } from "@/components/ds";
import { ExperienceBreadcrumbs } from "@/modules/experience/components/ExperienceBreadcrumbs";
import { ExperienceStatGrid } from "@/modules/experience/design-system";
export const ExecutiveDashboard = function ExperienceExecutiveDashboard() {
const { tenantId } = useTenantId();
const caps = useExperienceCapabilities();
const metrics = useExperienceMetrics();
const countsQ = useQuery({
queryKey: ["experience", tenantId, "dashboard-counts"],
queryFn: async () => {
const [workspaces, sites, pages, forms, releases, audit] = await Promise.all([
experienceApi.workspaces.list(tenantId!),
experienceApi.sites.list(tenantId!),
experienceApi.pages.list(tenantId!),
experienceApi.forms.list(tenantId!),
experienceApi.publishReleases.list(tenantId!),
experienceApi.audit.list(tenantId!, { limit: 10 }),
]);
return {
workspaces: workspaces.length,
sites: sites.length,
pages: pages.length,
forms: forms.length,
releases: releases.length,
recentPages: pages.slice(0, 5),
recentSites: sites.slice(0, 5),
audit: audit.slice(0, 10),
};
},
enabled: !!tenantId,
});
if (!tenantId || countsQ.isLoading || caps.isLoading) return <ExperiencePageLoader />;
if (countsQ.error) return <ExperiencePageError error={countsQ.error} onRetry={() => countsQ.refetch()} />;
const c = countsQ.data!;
const chartData = [
{ name: "فضاها", value: c.workspaces },
{ name: "سایت", value: c.sites },
{ name: "صفحات", value: c.pages },
{ name: "فرم", value: c.forms },
{ name: "انتشار", value: c.releases },
];
return (
<div className="space-y-6">
<ExperienceBreadcrumbs current="داشبورد اجرایی" />
<PageHeader
title="داشبورد اجرایی"
description="نمای کلی تربت پیجز — سایت، صفحه، فرم و انتشار."
actions={<Badge tone="success">Torbat Pages v{caps.data?.version}</Badge>}
/>
<ExperienceStatGrid
stats={[
{ label: "فضاهای کاری", value: String(c.workspaces) },
{ label: "سایت‌ها", value: String(c.sites) },
{ label: "صفحات", value: String(c.pages) },
{ label: "فرم‌ها", value: String(c.forms) },
]}
/>
<div className="grid gap-4 lg:grid-cols-2">
<Card>
<CardContent className="p-4">
<h3 className="mb-4 text-sm font-semibold text-secondary">توزیع منابع</h3>
<div className="h-48">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData}>
<XAxis dataKey="name" tick={{ fontSize: 11 }} />
<YAxis allowDecimals={false} tick={{ fontSize: 11 }} />
<Tooltip />
<Bar dataKey="value" fill="var(--experience-accent)" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<h3 className="mb-2 text-sm font-semibold text-secondary">اقدامات سریع</h3>
<div className="flex flex-wrap gap-2">
<Link href="/experience/pages"><Button variant="outline" size="sm">صفحات</Button></Link>
<Link href="/experience/sites"><Button variant="outline" size="sm">سایتها</Button></Link>
<Link href="/experience/forms"><Button variant="outline" size="sm">فرمها</Button></Link>
<Link href="/experience/publish-releases"><Button variant="outline" size="sm">انتشار</Button></Link>
<Link href="/experience/analytics"><Button variant="outline" size="sm">آنالیتیکس</Button></Link>
</div>
</CardContent>
</Card>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<Card>
<CardContent className="p-4">
<h3 className="mb-2 text-sm font-semibold">آخرین صفحات</h3>
<ul className="space-y-1 text-sm">
{c.recentPages.map((p) => (
<li key={String(p.id)}>
<Link href={`/experience/pages/${p.id}/builder`} className="text-[var(--experience-accent)] hover:underline">
{String(p.title ?? p.slug)}
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<h3 className="mb-2 text-sm font-semibold">فعالیت اخیر (ممیزی)</h3>
<ul className="space-y-1 text-xs text-[var(--muted)]">
{c.audit.map((a) => (
<li key={String(a.id)}>
{String(a.action)} {String(a.entity_type)} {new Date(String(a.created_at)).toLocaleString("fa-IR")}
</li>
))}
{c.audit.length === 0 ? <li>فعالیتی ثبت نشده</li> : null}
</ul>
</CardContent>
</Card>
</div>
{metrics.data ? (
<Card>
<CardContent className="p-4 text-xs text-[var(--muted)]">
فاز {metrics.data.phase} {metrics.data.note}
</CardContent>
</Card>
) : null}
</div>
);
};
export default ExecutiveDashboard;