csa-dashboard-sementara/csa-dashboard/app/(dashboard)/campaigns/page.tsx

806 lines
43 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { ColumnDef } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { DataTable } from "@/components/data-table/data-table";
import { EmptyState } from "@/components/empty-state";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useCampaignList, useCancelCampaign, useCampaignReport, useUpdateCampaign, useDeleteCampaign } from "@/modules/campaigns/hooks";
import { Campaign } from "@/modules/campaigns/schemas";
import { useToast } from "@/hooks/use-toast";
import { MoreHorizontal, Plus, Send, Trash, Calendar, Filter, Eye, CheckCircle2, XCircle, Clock, AlertCircle, FileText, TrendingUp, Target, Users, RefreshCw } from "lucide-react";
import { formatDateTime } from "@/lib/utils";
export default function CampaignsPage() {
const router = useRouter();
const { toast } = useToast();
const [statusFilter, setStatusFilter] = useState<string | undefined>(undefined);
const [isRefetching, setIsRefetching] = useState(false);
const { data, isLoading, refetch } = useCampaignList(statusFilter);
const cancelMutation = useCancelCampaign();
const updateMutation = useUpdateCampaign();
const deleteMutation = useDeleteCampaign();
const [cancelCampaign, setCancelCampaign] = useState<{ id: string; title: string } | null>(null);
const [selectedCampaign, setSelectedCampaign] = useState<Campaign | null>(null);
const [isDetailOpen, setIsDetailOpen] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);
const [editFormData, setEditFormData] = useState<{ title?: string; content?: string; date?: string; status?: "pending" | "completed" | "cancelled" | "failed" }>({});
const [reportCampaignId, setReportCampaignId] = useState<string | null>(null);
const [isReportOpen, setIsReportOpen] = useState(false);
const handleRefetch = async () => {
setIsRefetching(true);
try {
await refetch();
} finally {
setIsRefetching(false);
}
};
const handleCancel = async () => {
if (!cancelCampaign) return;
try {
await cancelMutation.mutateAsync(cancelCampaign.id);
toast({
title: "Campaign cancelled",
description: "Campaign has been cancelled successfully",
});
setCancelCampaign(null);
} catch (error) {
toast({
title: "Cancel failed",
description: "Failed to cancel campaign",
variant: "destructive",
});
setCancelCampaign(null);
}
};
const handleEdit = async () => {
if (!selectedCampaign || !editFormData.title) return;
try {
await updateMutation.mutateAsync({
id: selectedCampaign.UUID_ACP,
data: editFormData,
});
toast({
title: "Campaign updated",
description: "Campaign has been updated successfully",
});
setIsEditOpen(false);
setEditFormData({});
refetch();
} catch (error) {
toast({
title: "Update failed",
description: "Failed to update campaign",
variant: "destructive",
});
}
};
const handleDelete = async (campaignId: string) => {
try {
await deleteMutation.mutateAsync(campaignId);
toast({
title: "Campaign deleted",
description: "Campaign has been cancelled successfully",
});
refetch();
} catch (error) {
toast({
title: "Delete failed",
description: "Failed to delete campaign",
variant: "destructive",
});
}
};
const getStatusBadge = (status: string | undefined) => {
if (!status) {
return <Badge variant="default">Unknown</Badge>;
}
const variants: Record<string, "default" | "secondary" | "destructive"> = {
pending: "secondary",
completed: "default",
cancelled: "destructive",
failed: "destructive",
};
return (
<Badge variant={variants[status] || "default"}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Badge>
);
};
const columns: ColumnDef<Campaign>[] = [
{
accessorKey: "Title_ACP",
header: "Title",
cell: ({ row }) => (
<div className="font-medium">{row.original.Title_ACP || "-"}</div>
),
},
{
accessorKey: "Content_ACP",
header: "Content",
cell: ({ row }) => (
<div className="max-w-[200px] truncate text-muted-foreground">
{row.original.Content_ACP || "-"}
</div>
),
},
{
accessorKey: "Date_ACP",
header: "Scheduled Date",
cell: ({ row }) => row.original.Date_ACP ? formatDateTime(row.original.Date_ACP) : "-",
},
{
accessorKey: "Status_ACP",
header: "Status",
cell: ({ row }) => getStatusBadge(row.original.Status_ACP),
},
{
accessorKey: "TargetUsers_ACP",
header: "Target",
cell: ({ row }) => (
<div className="text-center font-mono">{row.original.TargetUsers_ACP}</div>
),
},
{
accessorKey: "SentCount_ACP",
header: "Sent",
cell: ({ row }) => (
<div className="text-center font-mono">{row.original.SentCount_ACP}</div>
),
},
{
accessorKey: "DeliveryRate_ACP",
header: "Delivery Rate",
cell: ({ row }) => {
const rate = row.original.DeliveryRate_ACP;
return (
<div className="text-center">
<span className={`font-semibold ${rate >= 90 ? 'text-green-600' : rate >= 70 ? 'text-yellow-600' : 'text-red-600'}`}>
{rate}%
</span>
</div>
);
},
},
{
accessorKey: "CreatedAt_ACP",
header: "Created",
cell: ({ row }) => (
<div className="text-sm text-muted-foreground">
{row.original.CreatedAt_ACP ? formatDateTime(row.original.CreatedAt_ACP) : "-"}
</div>
),
},
{
id: "actions",
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setSelectedCampaign(row.original);
setIsDetailOpen(true);
}}
>
<Eye className="mr-2 h-4 w-4" />
View Details
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setReportCampaignId(row.original.UUID_ACP);
setIsReportOpen(true);
}}
>
<FileText className="mr-2 h-4 w-4" />
View Report
</DropdownMenuItem>
{row.original.Status_ACP === "pending" && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
setSelectedCampaign(row.original);
setEditFormData({
title: row.original.Title_ACP,
content: row.original.Content_ACP,
date: row.original.Date_ACP,
});
setIsEditOpen(true);
}}
>
<MoreHorizontal className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setCancelCampaign({ id: row.original.UUID_ACP, title: row.original.Title_ACP })}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Cancel
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
),
},
];
if (isLoading) {
return <div>Loading...</div>;
}
const campaigns = data?.data || [];
const filterLabel = statusFilter
? statusFilter.charAt(0).toUpperCase() + statusFilter.slice(1)
: "All Status";
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Campaign Management</h1>
<p className="text-muted-foreground">
Manage and schedule notification campaigns
</p>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="icon"
onClick={() => handleRefetch()}
title="Refresh campaigns"
disabled={isRefetching || isLoading}
>
<RefreshCw className={`h-4 w-4 ${isRefetching || isLoading ? 'animate-spin-smooth' : ''}`} />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Filter className="mr-2 h-4 w-4" />
{filterLabel}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setStatusFilter(undefined)}>
All Status
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter("pending")}>
Pending
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter("completed")}>
Completed
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter("cancelled")}>
Cancelled
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter("failed")}>
Failed
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant="outline"
onClick={() => router.push("/campaigns/send")}
>
<Send className="mr-2 h-4 w-4" />
Send Single
</Button>
<Button onClick={() => router.push("/campaigns/setup")}>
<Calendar className="mr-2 h-4 w-4" />
Setup Campaign
</Button>
</div>
</div>
{campaigns.length === 0 ? (
<EmptyState
title="No campaigns yet"
description="Create your first campaign to get started"
action={
<div className="flex gap-2">
<Button
variant="outline"
onClick={() => router.push("/campaigns/send")}
>
<Send className="mr-2 h-4 w-4" />
Send Single
</Button>
<Button onClick={() => router.push("/campaigns/setup")}>
<Calendar className="mr-2 h-4 w-4" />
Setup Campaign
</Button>
</div>
}
/>
) : (
<DataTable columns={columns} data={campaigns} searchKey="Title_ACP" />
)}
<AlertDialog open={!!cancelCampaign} onOpenChange={(open) => !open && setCancelCampaign(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Cancel Campaign</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to cancel the campaign &quot;{cancelCampaign?.title}&quot;? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Keep Campaign</AlertDialogCancel>
<AlertDialogAction
onClick={handleCancel}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Cancel Campaign
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Dialog open={isEditOpen} onOpenChange={setIsEditOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Edit Campaign</DialogTitle>
<DialogDescription>
Update campaign details
</DialogDescription>
</DialogHeader>
{selectedCampaign && (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">Title</label>
<input
type="text"
value={editFormData.title || ""}
onChange={(e) => setEditFormData({ ...editFormData, title: e.target.value })}
className="w-full px-3 py-2 border rounded-md border-input bg-background"
placeholder="Campaign title"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Content</label>
<textarea
value={editFormData.content || ""}
onChange={(e) => setEditFormData({ ...editFormData, content: e.target.value })}
className="w-full px-3 py-2 border rounded-md border-input bg-background min-h-24"
placeholder="Campaign content"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Scheduled Date</label>
<input
type="datetime-local"
value={editFormData.date ? new Date(editFormData.date).toISOString().slice(0, 16) : ""}
onChange={(e) => setEditFormData({ ...editFormData, date: new Date(e.target.value).toISOString() })}
className="w-full px-3 py-2 border rounded-md border-input bg-background"
/>
</div>
<div className="flex gap-2 justify-end mt-6">
<Button variant="outline" onClick={() => setIsEditOpen(false)}>
Cancel
</Button>
<Button onClick={handleEdit} disabled={updateMutation.isPending}>
{updateMutation.isPending ? "Updating..." : "Update Campaign"}
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
<Dialog open={isDetailOpen} onOpenChange={setIsDetailOpen}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
Campaign Details
{selectedCampaign && getStatusBadge(selectedCampaign.Status_ACP)}
</DialogTitle>
<DialogDescription>
Complete information about this campaign
</DialogDescription>
</DialogHeader>
{selectedCampaign && (
<div className="space-y-6">
{/* Basic Info */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Basic Information</h3>
<div className="grid gap-3">
<div className="grid grid-cols-3 gap-2">
<span className="text-sm text-muted-foreground">Campaign ID:</span>
<span className="col-span-2 text-sm font-mono">{selectedCampaign.UUID_ACP}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-sm text-muted-foreground">Title:</span>
<span className="col-span-2 text-sm font-medium">{selectedCampaign.Title_ACP}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-sm text-muted-foreground">Content:</span>
<span className="col-span-2 text-sm">{selectedCampaign.Content_ACP}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-sm text-muted-foreground">Scheduled:</span>
<span className="col-span-2 text-sm">{formatDateTime(selectedCampaign.Date_ACP)}</span>
</div>
</div>
</div>
{/* Delivery Stats */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Delivery Statistics</h3>
<div className="grid gap-3 md:grid-cols-2">
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<CheckCircle2 className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Target Users</span>
</div>
<p className="text-2xl font-semibold">{selectedCampaign.TargetUsers_ACP}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Send className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Sent Count</span>
</div>
<p className="text-2xl font-semibold">{selectedCampaign.SentCount_ACP}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<span className="text-xs text-muted-foreground">Success</span>
</div>
<p className="text-2xl font-semibold text-green-600">{selectedCampaign.SuccessCount_ACP}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<XCircle className="h-4 w-4 text-red-600" />
<span className="text-xs text-muted-foreground">Failures</span>
</div>
<p className="text-2xl font-semibold text-red-600">{selectedCampaign.FailureCount_ACP}</p>
</div>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Delivery Rate</span>
<span className={`text-2xl font-bold ${selectedCampaign.DeliveryRate_ACP >= 90 ? 'text-green-600' :
selectedCampaign.DeliveryRate_ACP >= 70 ? 'text-yellow-600' :
'text-red-600'
}`}>
{selectedCampaign.DeliveryRate_ACP}%
</span>
</div>
</div>
</div>
{/* Timestamps */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Timeline</h3>
<div className="grid gap-2">
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Created:</span>
<span className="font-mono">{formatDateTime(selectedCampaign.CreatedAt_ACP)}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Updated:</span>
<span className="font-mono">{formatDateTime(selectedCampaign.UpdatedAt_ACP)}</span>
</div>
{selectedCampaign.SentAt_ACP && (
<div className="flex items-center gap-2 text-sm">
<Send className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Sent At:</span>
<span className="font-mono">{formatDateTime(selectedCampaign.SentAt_ACP)}</span>
</div>
)}
{selectedCampaign.CompletedAt_ACP && (
<div className="flex items-center gap-2 text-sm">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<span className="text-muted-foreground">Completed:</span>
<span className="font-mono">{formatDateTime(selectedCampaign.CompletedAt_ACP)}</span>
</div>
)}
</div>
</div>
{/* Error Message */}
{selectedCampaign.ErrorMessage_ACP && (
<div className="space-y-2">
<h3 className="text-sm font-semibold text-muted-foreground flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-600" />
Error Message
</h3>
<div className="glass border border-red-600/20 rounded-lg p-4 bg-red-50/50 dark:bg-red-950/20">
<p className="text-sm text-red-600 dark:text-red-400 font-mono">
{selectedCampaign.ErrorMessage_ACP}
</p>
</div>
</div>
)}
</div>
)}
</DialogContent>
</Dialog>
<CampaignReportDialog
campaignId={reportCampaignId}
isOpen={isReportOpen}
onClose={() => {
setIsReportOpen(false);
setReportCampaignId(null);
}}
/>
</div>
);
}
function CampaignReportDialog({ campaignId, isOpen, onClose }: { campaignId: string | null; isOpen: boolean; onClose: () => void }) {
const { data: reportData, isLoading } = useCampaignReport(campaignId || "");
const report = reportData?.data;
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Campaign Report
</DialogTitle>
<DialogDescription>
Detailed performance and delivery report
</DialogDescription>
</DialogHeader>
{isLoading ? (
<div className="flex items-center justify-center py-8">
<div className="text-center space-y-4">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
<p className="text-sm text-muted-foreground">Loading report...</p>
</div>
</div>
) : report ? (
<div className="space-y-6">
{/* Campaign Info */}
<div className="glass border rounded-lg p-4">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold">{report.campaign.title}</h3>
<p className="text-sm text-muted-foreground mt-1">{report.campaign.content}</p>
</div>
<Badge variant={report.campaign.status === "completed" ? "default" : report.campaign.status === "failed" ? "destructive" : "secondary"}>
{report.campaign.status}
</Badge>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-muted-foreground">Campaign ID:</span>
<p className="font-mono mt-1">{report.campaign.id}</p>
</div>
<div>
<span className="text-muted-foreground">Scheduled Date:</span>
<p className="mt-1">{formatDateTime(report.campaign.scheduledDate)}</p>
</div>
</div>
</div>
{/* Metrics */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Performance Metrics</h3>
<div className="grid gap-3 md:grid-cols-3">
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Clock className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Lead Time</span>
</div>
<p className="text-xl font-semibold">{report.metrics.leadTime}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Execution Time</span>
</div>
<p className="text-xl font-semibold">{report.metrics.executionTime}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Target className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Time Until Execution</span>
</div>
<p className="text-xl font-semibold">{report.metrics.timeUntilExecution}</p>
</div>
</div>
<div className="grid gap-3 md:grid-cols-3">
<div className="glass border rounded-lg p-3 flex items-center justify-between">
<span className="text-sm text-muted-foreground">Scheduled</span>
<Badge variant={report.metrics.isScheduled ? "default" : "secondary"}>
{report.metrics.isScheduled ? "Yes" : "No"}
</Badge>
</div>
<div className="glass border rounded-lg p-3 flex items-center justify-between">
<span className="text-sm text-muted-foreground">Overdue</span>
<Badge variant={report.metrics.isOverdue ? "destructive" : "default"}>
{report.metrics.isOverdue ? "Yes" : "No"}
</Badge>
</div>
<div className="glass border rounded-lg p-3 flex items-center justify-between">
<span className="text-sm text-muted-foreground">Avg Delivery</span>
<span className="text-sm font-semibold">{report.metrics.avgDeliveryTime}</span>
</div>
</div>
</div>
{/* Delivery Stats */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Delivery Statistics</h3>
<div className="grid gap-3 md:grid-cols-4">
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Target Users</span>
</div>
<p className="text-2xl font-semibold">{report.delivery.targetUsers}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Send className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">Sent</span>
</div>
<p className="text-2xl font-semibold">{report.delivery.sentCount}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<span className="text-xs text-muted-foreground">Success</span>
</div>
<p className="text-2xl font-semibold text-green-600">{report.delivery.successCount}</p>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<XCircle className="h-4 w-4 text-red-600" />
<span className="text-xs text-muted-foreground">Failed</span>
</div>
<p className="text-2xl font-semibold text-red-600">{report.delivery.failureCount}</p>
</div>
</div>
<div className="glass border rounded-lg p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Overall Delivery Rate</span>
<span className="text-2xl font-bold">{report.delivery.deliveryRate}</span>
</div>
</div>
</div>
{/* Status Breakdown */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Status Breakdown</h3>
<div className="grid gap-3 md:grid-cols-4">
<div className="border rounded-lg p-3 text-center">
<p className="text-2xl font-semibold">{report.delivery.statusBreakdown.pending}</p>
<p className="text-xs text-muted-foreground mt-1">Pending</p>
</div>
<div className="border rounded-lg p-3 text-center">
<p className="text-2xl font-semibold">{report.delivery.statusBreakdown.sent}</p>
<p className="text-xs text-muted-foreground mt-1">Sent</p>
</div>
<div className="border rounded-lg p-3 text-center">
<p className="text-2xl font-semibold text-green-600">{report.delivery.statusBreakdown.delivered}</p>
<p className="text-xs text-muted-foreground mt-1">Delivered</p>
</div>
<div className="border rounded-lg p-3 text-center">
<p className="text-2xl font-semibold text-red-600">{report.delivery.statusBreakdown.failed}</p>
<p className="text-xs text-muted-foreground mt-1">Failed</p>
</div>
</div>
</div>
{/* Timeline */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Timeline</h3>
<div className="space-y-2 border rounded-lg p-4">
<div className="flex items-center gap-3 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground w-24">Created:</span>
<span className="font-mono">{formatDateTime(report.timeline.created)}</span>
</div>
<div className="flex items-center gap-3 text-sm">
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground w-24">Scheduled:</span>
<span className="font-mono">{formatDateTime(report.timeline.scheduled)}</span>
</div>
<div className="flex items-center gap-3 text-sm">
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground w-24">Executed:</span>
<span className="font-mono">{formatDateTime(report.timeline.executed)}</span>
</div>
{report.timeline.sent && (
<div className="flex items-center gap-3 text-sm">
<Send className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground w-24">Sent:</span>
<span className="font-mono">{formatDateTime(report.timeline.sent)}</span>
</div>
)}
{report.timeline.completed && (
<div className="flex items-center gap-3 text-sm">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<span className="text-muted-foreground w-24">Completed:</span>
<span className="font-mono">{formatDateTime(report.timeline.completed)}</span>
</div>
)}
</div>
</div>
{/* Delivery Records */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground">Delivery Records</h3>
<div className="glass border rounded-lg p-4 text-center">
<p className="text-3xl font-bold">{report.deliveryRecords.total}</p>
<p className="text-sm text-muted-foreground mt-1">Total Delivery Records</p>
</div>
</div>
{/* Error Message */}
{report.campaign.errorMessage && (
<div className="space-y-2">
<h3 className="text-sm font-semibold text-muted-foreground flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-600" />
Error Message
</h3>
<div className="glass border border-red-600/20 rounded-lg p-4 bg-red-50/50 dark:bg-red-950/20">
<p className="text-sm text-red-600 dark:text-red-400 font-mono">
{report.campaign.errorMessage}
</p>
</div>
</div>
)}
</div>
) : (
<div className="text-center py-8 text-muted-foreground">
No report data available
</div>
)}
</DialogContent>
</Dialog>
);
}