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>
100 lines
4.3 KiB
TypeScript
100 lines
4.3 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { communicationApi } from "@/modules/communication/services/communication-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { CommunicationPageLoader, CommunicationPageError, exportToCsv } from "@/modules/communication/pages/shared";
|
||
import { CommunicationTablePage, CommunicationStatusChip } from "@/modules/communication/design-system";
|
||
import { CommunicationBreadcrumbs } from "@/modules/communication/components/CommunicationBreadcrumbs";
|
||
import { Button, Drawer, Badge } from "@/components/ds";
|
||
import { Timeline } from "@/src/shared/ui";
|
||
import { Download, RefreshCw } from "lucide-react";
|
||
import { useCommunicationPermissions } from "@/modules/communication/hooks/useCommunicationPermissions";
|
||
import { PermissionDeniedState } from "@/src/shared/ui";
|
||
|
||
export function MessagesPage() {
|
||
const { tenantId } = useTenantId();
|
||
const perms = useCommunicationPermissions();
|
||
const [status, setStatus] = useState<string>("");
|
||
const [detailId, setDetailId] = useState<string | null>(null);
|
||
|
||
const listQ = useQuery({
|
||
queryKey: ["communication", tenantId, "messages", status],
|
||
queryFn: () => communicationApi.messages.list(tenantId!, status ? { status } : undefined),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
const timelineQ = useQuery({
|
||
queryKey: ["communication", tenantId, "timeline", detailId],
|
||
queryFn: () => communicationApi.messages.timeline(tenantId!, detailId!),
|
||
enabled: !!tenantId && !!detailId,
|
||
});
|
||
|
||
if (!tenantId || listQ.isLoading) return <CommunicationPageLoader />;
|
||
if (listQ.error) return <CommunicationPageError error={listQ.error} onRetry={() => listQ.refetch()} />;
|
||
if (!perms.can("communication.messages.view")) return <PermissionDeniedState />;
|
||
|
||
const rows = listQ.data ?? [];
|
||
|
||
return (
|
||
<>
|
||
<CommunicationTablePage
|
||
title="تاریخچه پیام"
|
||
description="پیامهای SMS با وضعیت تحویل"
|
||
breadcrumb="تاریخچه پیام"
|
||
columns={[
|
||
{ key: "to_address", header: "گیرنده" },
|
||
{ key: "status", header: "وضعیت", render: (r) => <CommunicationStatusChip status={String(r.status)} /> },
|
||
{ key: "channel", header: "کانال" },
|
||
{ key: "sent_at", header: "ارسال", render: (r) => (r.sent_at ? new Date(String(r.sent_at)).toLocaleString("fa-IR") : "—") },
|
||
{
|
||
key: "id",
|
||
header: "جزئیات",
|
||
render: (r) => (
|
||
<Button variant="ghost" size="sm" onClick={() => setDetailId(String(r.id))}>
|
||
Timeline
|
||
</Button>
|
||
),
|
||
},
|
||
]}
|
||
rows={rows}
|
||
toolbar={
|
||
<select
|
||
className="rounded-lg border border-[var(--border)] bg-[var(--surface)] px-3 py-2 text-sm"
|
||
value={status}
|
||
onChange={(e) => setStatus(e.target.value)}
|
||
>
|
||
<option value="">همه وضعیتها</option>
|
||
<option value="queued">در صف</option>
|
||
<option value="sent">ارسالشده</option>
|
||
<option value="delivered">تحویل</option>
|
||
<option value="failed">ناموفق</option>
|
||
</select>
|
||
}
|
||
actions={
|
||
<div className="flex gap-2">
|
||
<Button variant="outline" size="sm" onClick={() => listQ.refetch()}><RefreshCw className="ml-1 h-4 w-4" />بروزرسانی</Button>
|
||
<Button variant="outline" size="sm" onClick={() => exportToCsv("messages", rows, ["to_address", "status", "channel"])}><Download className="ml-1 h-4 w-4" />CSV</Button>
|
||
</div>
|
||
}
|
||
/>
|
||
<Drawer open={!!detailId} onClose={() => setDetailId(null)} title="Timeline تحویل">
|
||
{timelineQ.isLoading ? <CommunicationPageLoader label="Timeline…" /> : null}
|
||
{timelineQ.data ? (
|
||
<Timeline
|
||
items={timelineQ.data.map((e) => ({
|
||
id: String(e.id),
|
||
title: String(e.status),
|
||
subtitle: e.detail ? String(e.detail) : undefined,
|
||
timestamp: String(e.created_at ?? new Date().toISOString()),
|
||
}))}
|
||
/>
|
||
) : null}
|
||
</Drawer>
|
||
</>
|
||
);
|
||
}
|
||
|
||
export default MessagesPage;
|