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>
97 lines
4.0 KiB
TypeScript
97 lines
4.0 KiB
TypeScript
"use client";
|
||
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import Link from "next/link";
|
||
import { communicationApi } from "@/modules/communication/services/communication-api";
|
||
import { useAuth } from "@/hooks/useAuth";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import {
|
||
useCommunicationCapabilities,
|
||
useCommunicationMonitoring,
|
||
} from "@/modules/communication/hooks/useCommunicationCapabilities";
|
||
import { CommunicationPageLoader, CommunicationPageError } from "@/modules/communication/pages/shared";
|
||
import { PageHeader, StatCard, Card, CardContent, Button, Badge } from "@/components/ds";
|
||
import { CommunicationBreadcrumbs } from "@/modules/communication/components/CommunicationBreadcrumbs";
|
||
|
||
export function ExecutiveDashboard() {
|
||
const { tenantId } = useTenantId();
|
||
const { isAuthenticated, loading: authLoading } = useAuth();
|
||
const caps = useCommunicationCapabilities();
|
||
const monitoring = useCommunicationMonitoring(tenantId);
|
||
|
||
const countsQ = useQuery({
|
||
queryKey: ["communication", tenantId, "exec-counts"],
|
||
queryFn: async () => {
|
||
const [providers, templates, messages] = await Promise.all([
|
||
communicationApi.providers.list(tenantId!),
|
||
communicationApi.templates.list(tenantId!),
|
||
communicationApi.messages.list(tenantId!, { limit: 500 }),
|
||
]);
|
||
return {
|
||
providers: providers.length,
|
||
templates: templates.length,
|
||
messages: messages.length,
|
||
delivered: messages.filter((m) => m.status === "delivered").length,
|
||
};
|
||
},
|
||
enabled: !!tenantId && isAuthenticated && !authLoading,
|
||
});
|
||
|
||
if (authLoading || !tenantId || countsQ.isLoading || caps.isLoading) {
|
||
return <CommunicationPageLoader />;
|
||
}
|
||
if (countsQ.error) return <CommunicationPageError error={countsQ.error} onRetry={() => countsQ.refetch()} />;
|
||
|
||
const c = countsQ.data!;
|
||
const stats = monitoring.data;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<CommunicationBreadcrumbs current="داشبورد اجرایی" />
|
||
<PageHeader
|
||
title="داشبورد اجرایی"
|
||
description="نمای کلی Torbat Communication — SMS Production Ready"
|
||
actions={
|
||
<Badge tone="success">v{caps.data?.version} · SMS MVP</Badge>
|
||
}
|
||
/>
|
||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||
<StatCard label="ارائهدهندگان" value={String(c.providers)} />
|
||
<StatCard label="قالبها" value={String(c.templates)} />
|
||
<StatCard label="پیامها" value={String(c.messages)} />
|
||
<StatCard label="تحویلشده" value={String(c.delivered)} />
|
||
</div>
|
||
{stats ? (
|
||
<div className="grid gap-4 sm:grid-cols-2">
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<p className="mb-2 text-sm font-semibold">صف پیام</p>
|
||
<pre className="text-xs text-[var(--muted)]">
|
||
{JSON.stringify(stats.queue_by_status, null, 2)}
|
||
</pre>
|
||
</CardContent>
|
||
</Card>
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<p className="mb-2 text-sm font-semibold">وضعیت پیامها</p>
|
||
<pre className="text-xs text-[var(--muted)]">
|
||
{JSON.stringify(stats.messages_by_status, null, 2)}
|
||
</pre>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
) : null}
|
||
<Card>
|
||
<CardContent className="flex flex-wrap gap-2 p-4">
|
||
<Link href="/communication/sms/providers"><Button variant="outline">ارائهدهندگان</Button></Link>
|
||
<Link href="/communication/sms/send"><Button variant="outline">ارسال SMS</Button></Link>
|
||
<Link href="/communication/sms/messages"><Button variant="outline">تاریخچه</Button></Link>
|
||
<Link href="/communication/capabilities"><Button variant="outline">قابلیتها</Button></Link>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default ExecutiveDashboard;
|