import { useMemo } from 'react' import { Link, useParams } from 'react-router-dom' import { toast } from 'sonner' import { Area, AreaChart, CartesianGrid, Cell, Legend, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts' import { Copy, Download, MousePointerClick, PauseCircle, Send, Sparkles, Users } from 'lucide-react' import { DetailPageLayout } from '@/layouts/DetailPageLayout' import { ActivityTimeline, DetailSection } from '@/components/detail/ActivityTimeline' import { KeyValueGrid } from '@/components/detail/KeyValueGrid' import { ChartCard } from '@/components/charts/ChartCard' import { StatCard } from '@/components/dashboard/StatCard' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { campaignRecipients, campaigns } from '@/mocks/data' import { campaignChannelBreakdown, campaignDailySends } from '@/mocks/charts' import { formatEtb } from '@/lib/utils' import type { Campaign, CampaignRecipient } from '@/mocks/data' import type { TimelineEvent } from '@/components/detail/ActivityTimeline' const statusTone: Record = { sent: 'success', sending: 'default', scheduled: 'warning', draft: 'muted', } const recipientTone: Record = { redeemed: 'success', clicked: 'default', opened: 'default', delivered: 'warning', bounced: 'danger', } export function CampaignDetailPage() { const { id } = useParams() const campaign = campaigns.find((c) => c.id === id) const recipients = useMemo(() => (id ? (campaignRecipients[id] ?? []) : []), [id]) const breakdown = useMemo(() => { if (!campaign) return [] const preset = id ? campaignChannelBreakdown[id] : undefined if (preset) return preset return [ { name: 'Delivered', value: campaign.delivered, fill: '#A8C73A' }, { name: 'Bounced', value: campaign.bounced, fill: '#E28A1A' }, ].filter((slice) => slice.value > 0) }, [campaign, id]) const daily = useMemo(() => (id ? (campaignDailySends[id] ?? []) : []), [id]) const funnel = useMemo(() => { if (!campaign) return null const pct = (value: number, base: number) => (base > 0 ? Math.round((value / base) * 100) : 0) return { deliveryRate: pct(campaign.delivered, campaign.recipients), openRate: pct(campaign.opened, campaign.delivered), clickRate: pct(campaign.clicked, campaign.delivered), redeemRate: pct(campaign.redeemed, campaign.delivered), } }, [campaign]) const timeline = useMemo(() => { if (!campaign) return [] const events: TimelineEvent[] = [ { id: 'created', at: campaign.createdAt, title: 'Campaign created', detail: `${campaign.createdBy} drafted this ${campaign.channel} campaign`, }, ] if (campaign.scheduledFor) { events.push({ id: 'scheduled', at: campaign.scheduledFor, title: 'Scheduled to send', detail: `${campaign.recipients.toLocaleString()} recipients queued`, tone: 'warning', }) } if (campaign.sentAt) { events.push({ id: 'sent', at: campaign.sentAt, title: campaign.status === 'sending' ? 'Sending started' : 'Campaign sent', detail: `${campaign.delivered.toLocaleString()} delivered · ${campaign.bounced.toLocaleString()} bounced`, tone: campaign.bounced > 0 ? 'warning' : 'success', }) if (campaign.opened > 0) { events.push({ id: 'opened', at: new Date(new Date(campaign.sentAt).getTime() + 45 * 60 * 1000).toISOString(), title: 'First opens recorded', detail: `${campaign.opened.toLocaleString()} opens · ${campaign.clicked.toLocaleString()} clicks`, }) } if (campaign.redeemed > 0) { events.push({ id: 'redeemed', at: new Date(new Date(campaign.sentAt).getTime() + 6 * 60 * 60 * 1000).toISOString(), title: 'Redemptions attributed', detail: `${campaign.redeemed.toLocaleString()} orders traced back to this campaign`, tone: 'success', }) } } return events.sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime()) }, [campaign]) if (!campaign || !funnel) { return ( ) } const canSend = campaign.status === 'draft' || campaign.status === 'scheduled' return ( {campaign.status}} actions={ <> {canSend ? ( ) : ( )} } sidebar={ } >
{campaign.channel} }, { label: 'Segment', value: ( {campaign.segmentName} ), }, { label: 'Status', value: {campaign.status} }, { label: 'Recipients', value: campaign.recipients.toLocaleString() }, { label: 'Budget', value: formatEtb(campaign.budgetEtb) }, { label: 'Created by', value: campaign.createdBy }, { label: 'Created at', value: new Date(campaign.createdAt).toLocaleString() }, { label: campaign.sentAt ? 'Sent at' : 'Scheduled for', value: campaign.sentAt ? new Date(campaign.sentAt).toLocaleString() : campaign.scheduledFor ? new Date(campaign.scheduledFor).toLocaleString() : '—', }, { label: 'Bounced', value: campaign.bounced.toLocaleString() }, ]} />

Message

{campaign.message}

0 ? 'grid gap-4 xl:grid-cols-2' : 'grid gap-4'}> {breakdown.length === 0 ? (
No delivery data yet
) : ( {breakdown.map((slice) => ( ))} Number(v).toLocaleString()} /> )}
{daily.length > 0 ? ( Number(v).toLocaleString()} /> ) : null}
Name Phone Status Sent at {recipients.length === 0 ? ( No recipient sample available for this campaign ) : ( recipients.map((r) => ( {r.customerId ? ( {r.name} ) : ( r.name )} {r.phone} {r.status} {new Date(r.sentAt).toLocaleString()} )) )}
) }