"use client"; /* eslint-disable react-hooks/set-state-in-effect */ import { useState, useEffect } from "react"; import { getMessageLogs, type MessageLogEntry } from "@/actions/communications/send"; import { formatDate } from "@/lib/format-date"; import { AdminButton } from "./design-system"; const PAGE_SIZE = 20; // Icon components const Icons = { list: (className: string) => ( ), search: (className: string) => ( ), refresh: (className: string) => ( ), check: (className: string) => ( ), x: (className: string) => ( ), eye: (className: string) => ( ), mousePointer: (className: string) => ( ), messageSquare: (className: string) => ( ), send: (className: string) => ( ), mail: (className: string) => ( ), clock: (className: string) => ( ), }; // Status color mapping const STATUS_COLORS: Record = { queued: { bg: "bg-stone-100", text: "text-stone-600", dot: "bg-stone-400" }, sent: { bg: "bg-blue-100", text: "text-blue-700", dot: "bg-blue-500" }, delivered: { bg: "bg-emerald-100", text: "text-emerald-700", dot: "bg-emerald-500" }, opened: { bg: "bg-purple-100", text: "text-purple-700", dot: "bg-purple-500" }, clicked: { bg: "bg-amber-100", text: "text-amber-700", dot: "bg-amber-500" }, bounced: { bg: "bg-red-100", text: "text-red-600", dot: "bg-red-500" }, failed: { bg: "bg-red-100", text: "text-red-600", dot: "bg-red-500" }, unsubscribed: { bg: "bg-orange-100", text: "text-orange-700", dot: "bg-orange-500" }, }; // Method color mapping const METHOD_COLORS: Record = { email: { bg: "bg-blue-100", text: "text-blue-700", dot: "bg-blue-500" }, sms: { bg: "bg-green-100", text: "text-green-700", dot: "bg-green-500" }, push: { bg: "bg-amber-100", text: "text-amber-700", dot: "bg-amber-500" }, internal: { bg: "bg-stone-100", text: "text-stone-600", dot: "bg-stone-400" }, }; // Empty state component function LogsEmptyState({ hasFilters }: { hasFilters: boolean }) { return (

{hasFilters ? "No messages match your filters" : "No message logs yet"}

{hasFilters ? "Try adjusting your search or status filter to find messages." : "Send your first campaign to start tracking message delivery and engagement."}

); } // Loading skeleton function LogSkeleton() { return ( <> {[1, 2, 3, 4, 5].map((i) => (
))} ); } // Status badge component function StatusBadge({ status }: { status: string }) { const colors = STATUS_COLORS[status] || STATUS_COLORS.queued; return ( {status} ); } // Method badge component function MethodBadge({ method }: { method: string }) { const colors = METHOD_COLORS[method] || METHOD_COLORS.internal; return ( {method} ); } // Engagement indicator function EngagementIndicator({ log }: { log: MessageLogEntry }) { const hasEngagement = log.delivered_at || log.opened_at || log.clicked_at || log.bounced_at; if (!hasEngagement) { return No engagement yet; } return (
{log.delivered_at && ( Delivered )} {log.opened_at && ( {Icons.eye("w-3.5 h-3.5")} Opened )} {log.clicked_at && ( {Icons.mousePointer("w-3.5 h-3.5")} Clicked )} {log.bounced_at && ( {Icons.x("w-3.5 h-3.5")} Bounced )}
); } export default function MessageLogPanel({ brandId }: { brandId?: string }) { const [logs, setLogs] = useState([]); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [page, setPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const fetchLogs = async () => { if (!brandId) return; setIsLoading(true); const result = await getMessageLogs({ brandId, status: statusFilter === "all" ? undefined : statusFilter, limit: 100, }); if (result.success) { setLogs(result.logs); } setIsLoading(false); }; useEffect(() => { fetchLogs(); }, [brandId, statusFilter]); // Filter logs based on search const filteredLogs = search ? logs.filter((l: MessageLogEntry) => l.customer_email?.toLowerCase().includes(search.toLowerCase()) || l.subject?.toLowerCase().includes(search.toLowerCase()) || l.status?.toLowerCase().includes(search.toLowerCase()) ) : logs; // Calculate stats const stats = { total: logs.length, delivered: logs.filter((l: MessageLogEntry) => l.status === "delivered" || l.delivered_at).length, failed: logs.filter((l: MessageLogEntry) => l.status === "failed" || l.status === "bounced").length, pending: logs.filter((l: MessageLogEntry) => l.status === "queued" || l.status === "sent").length, }; // Paginate const totalPages = Math.max(1, Math.ceil(filteredLogs.length / PAGE_SIZE)); const paginatedLogs = filteredLogs.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); const handleRefresh = () => { fetchLogs(); setPage(1); }; const hasFilters = search.length > 0 || statusFilter !== "all"; return (
{/* Header */}
{Icons.messageSquare("h-6 h-6 text-white")}

Message Logs

{isLoading ? "Loading..." : `${filteredLogs.length} message${filteredLogs.length !== 1 ? "s" : ""}`}

Refresh
{/* Stats Cards */}

Total

{stats.total}

Delivered

{stats.delivered}

Failed

{stats.failed}

Pending

{stats.pending}

{/* Filters */}
{Icons.search("h-5 w-5 text-stone-400")}
{ setSearch(e.target.value); setPage(1); }} className="w-full pl-11 pr-4 py-2.5 text-sm border border-stone-200 rounded-xl bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-all" />
{/* Table */} {isLoading ? (
Sent At Recipient Method Subject Status Engagement
) : paginatedLogs.length === 0 ? (
) : ( <> {/* Desktop table */}
{paginatedLogs.map((log: MessageLogEntry) => ( ))}
Sent At Recipient Method Subject Status Engagement
{log.sent_at ? formatDate(log.sent_at) : "—"} {log.customer_email ?? "—"} {log.subject ?? "—"}
{log.error_message && (

{log.error_message}

)}
{/* Mobile cards */}
{paginatedLogs.map((log: MessageLogEntry) => (
{log.customer_email ?? "—"}
{log.subject ?? "—"}
{log.sent_at ? formatDate(log.sent_at) : "—"}
))}
{/* Pagination */} {totalPages > 1 && (

Showing {(page - 1) * PAGE_SIZE + 1} to {Math.min(page * PAGE_SIZE, filteredLogs.length)} of {filteredLogs.length}

{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const pageNum = i + 1; return ( ); })}
)} )}
); }