Ship the full Communication portal (real SMS API, feature-locked future channels), BFF proxy, docs, snapshot, and phase handover. Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
"use client";
|
||
|
||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||
import { toast } from "sonner";
|
||
import { communicationApi } from "@/modules/communication/services/communication-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { CommunicationPageLoader, CommunicationPageError } from "@/modules/communication/pages/shared";
|
||
import { PageHeader, Card, CardContent, Button, StatCard } from "@/components/ds";
|
||
import { CommunicationBreadcrumbs } from "@/modules/communication/components/CommunicationBreadcrumbs";
|
||
import { CommunicationTablePage, CommunicationStatusChip } from "@/modules/communication/design-system";
|
||
|
||
export function QueuePage() {
|
||
const { tenantId } = useTenantId();
|
||
|
||
const statsQ = useQuery({
|
||
queryKey: ["communication", tenantId, "queue-stats"],
|
||
queryFn: () => communicationApi.messages.queue.stats(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
const dlqQ = useQuery({
|
||
queryKey: ["communication", tenantId, "dlq"],
|
||
queryFn: () => communicationApi.messages.queue.deadLetters(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
const processM = useMutation({
|
||
mutationFn: () => communicationApi.messages.queue.process(tenantId!, 50),
|
||
onSuccess: (r) => {
|
||
toast.success(`${r.processed} مورد پردازش شد`);
|
||
statsQ.refetch();
|
||
dlqQ.refetch();
|
||
},
|
||
onError: (e: Error) => toast.error(e.message),
|
||
});
|
||
|
||
if (!tenantId || statsQ.isLoading) return <CommunicationPageLoader />;
|
||
if (statsQ.error) return <CommunicationPageError error={statsQ.error} onRetry={() => statsQ.refetch()} />;
|
||
|
||
const stats = statsQ.data as Record<string, unknown>;
|
||
const dlq = dlqQ.data ?? [];
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<CommunicationBreadcrumbs current="صف SMS" />
|
||
<PageHeader
|
||
title="صف و DLQ"
|
||
description="پردازش دستی صف — worker اختیاری"
|
||
actions={<Button onClick={() => processM.mutate()} loading={processM.isPending}>پردازش صف</Button>}
|
||
/>
|
||
<div className="grid gap-4 sm:grid-cols-3">
|
||
{Object.entries((stats as { by_status?: Record<string, number> }).by_status ?? stats).slice(0, 6).map(([k, v]) => (
|
||
typeof v === "number" ? <StatCard key={k} label={k} value={String(v)} /> : null
|
||
))}
|
||
</div>
|
||
<CommunicationTablePage
|
||
title="Dead Letter Queue"
|
||
columns={[
|
||
{ key: "message_id", header: "Message ID" },
|
||
{ key: "status", header: "وضعیت", render: (r) => <CommunicationStatusChip status={String(r.status)} /> },
|
||
{ key: "attempt", header: "تلاش" },
|
||
{ key: "last_error", header: "خطا" },
|
||
]}
|
||
rows={dlq}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default QueuePage;
|