import React, { useState, useEffect } from 'react'; import { Mail, Plus, BarChart3, FileText, X, Layers, Target } from 'lucide-react'; import { emailCampaignService, Campaign, CampaignSegment, EmailTemplate, DripSequence, CampaignAnalytics } from '../../services/api/emailCampaignService'; import { toast } from 'react-toastify'; import Loading from '../../components/common/Loading'; import Pagination from '../../components/common/Pagination'; import { formatDate } from '../../utils/format'; type CampaignTab = 'campaigns' | 'segments' | 'templates' | 'drip-sequences' | 'analytics'; const EmailCampaignManagementPage: React.FC = () => { const [activeTab, setActiveTab] = useState('campaigns'); const [loading, setLoading] = useState(false); const [campaigns, setCampaigns] = useState([]); const [segments, setSegments] = useState([]); const [templates, setTemplates] = useState([]); const [dripSequences, setDripSequences] = useState([]); useState(null); const [analytics, setAnalytics] = useState(null); const [showCampaignModal, setShowCampaignModal] = useState(false); const [showSegmentModal, setShowSegmentModal] = useState(false); const [showTemplateModal, setShowTemplateModal] = useState(false); const [showDripModal, setShowDripModal] = useState(false); const [editingItem, setEditingItem] = useState(null); const [dripForm, setDripForm] = useState({ name: '', description: '', trigger_event: '' }); const [filters, setFilters] = useState({ status: '', campaign_type: '' }); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [campaignForm, setCampaignForm] = useState({ name: '', subject: '', html_content: '', text_content: '', campaign_type: 'newsletter', segment_id: undefined as number | undefined, scheduled_at: '', template_id: undefined as number | undefined, from_name: '', from_email: '', track_opens: true, track_clicks: true }); const [segmentForm, setSegmentForm] = useState({ name: '', description: '', criteria: { role: '', has_bookings: undefined as boolean | undefined, is_vip: undefined as boolean | undefined, last_booking_days: undefined as number | undefined } }); const [templateForm, setTemplateForm] = useState({ name: '', subject: '', html_content: '', text_content: '', category: '' }); useEffect(() => { if (activeTab === 'campaigns') { fetchCampaigns(); } else if (activeTab === 'segments') { fetchSegments(); } else if (activeTab === 'templates') { fetchTemplates(); } else if (activeTab === 'drip-sequences') { fetchDripSequences(); } }, [activeTab, filters, currentPage]); const fetchCampaigns = async () => { setLoading(true); try { const data = await emailCampaignService.getCampaigns({ status: filters.status || undefined, campaign_type: filters.campaign_type || undefined, limit: 20, offset: (currentPage - 1) * 20 }); setCampaigns(data); setTotalPages(Math.ceil(data.length / 20)); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to fetch campaigns'); } finally { setLoading(false); } }; const fetchSegments = async () => { setLoading(true); try { const data = await emailCampaignService.getSegments(); setSegments(data); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to fetch segments'); } finally { setLoading(false); } }; const fetchTemplates = async () => { setLoading(true); try { const data = await emailCampaignService.getTemplates(); setTemplates(data); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to fetch templates'); } finally { setLoading(false); } }; const fetchDripSequences = async () => { setLoading(true); try { const data = await emailCampaignService.getDripSequences(); setDripSequences(data); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to fetch drip sequences'); } finally { setLoading(false); } }; const handleCreateCampaign = async () => { try { if (editingItem) { await emailCampaignService.updateCampaign(editingItem.id, campaignForm); toast.success('Campaign updated'); } else { await emailCampaignService.createCampaign(campaignForm); toast.success('Campaign created'); } setShowCampaignModal(false); resetCampaignForm(); fetchCampaigns(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to save campaign'); } }; const handleSendCampaign = async (campaignId: number) => { if (!window.confirm('Are you sure you want to send this campaign?')) return; try { const result = await emailCampaignService.sendCampaign(campaignId); toast.success(`Campaign sent! ${result.sent} emails sent, ${result.failed} failed`); fetchCampaigns(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to send campaign'); } }; const handleViewAnalytics = async (campaignId: number) => { try { const data = await emailCampaignService.getCampaignAnalytics(campaignId); setAnalytics(data); setActiveTab('analytics'); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to fetch analytics'); } }; const handleCreateSegment = async () => { try { // Build criteria object const criteria: any = {}; if (segmentForm.criteria.role) criteria.role = segmentForm.criteria.role; if (segmentForm.criteria.has_bookings !== undefined) criteria.has_bookings = segmentForm.criteria.has_bookings; if (segmentForm.criteria.is_vip !== undefined) criteria.is_vip = segmentForm.criteria.is_vip; if (segmentForm.criteria.last_booking_days) criteria.last_booking_days = segmentForm.criteria.last_booking_days; await emailCampaignService.createSegment({ name: segmentForm.name, description: segmentForm.description, criteria }); toast.success('Segment created'); setShowSegmentModal(false); resetSegmentForm(); fetchSegments(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to create segment'); } }; const handleCreateTemplate = async () => { try { await emailCampaignService.createTemplate(templateForm); toast.success('Template created'); setShowTemplateModal(false); resetTemplateForm(); fetchTemplates(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to create template'); } }; const handleCreateDripSequence = async () => { try { await emailCampaignService.createDripSequence(dripForm); toast.success('Drip sequence created'); setShowDripModal(false); resetDripForm(); fetchDripSequences(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to create drip sequence'); } }; const resetCampaignForm = () => { setCampaignForm({ name: '', subject: '', html_content: '', text_content: '', campaign_type: 'newsletter', segment_id: undefined, scheduled_at: '', template_id: undefined, from_name: '', from_email: '', track_opens: true, track_clicks: true }); setEditingItem(null); }; const resetSegmentForm = () => { setSegmentForm({ name: '', description: '', criteria: { role: '', has_bookings: undefined, is_vip: undefined, last_booking_days: undefined } }); }; const resetTemplateForm = () => { setTemplateForm({ name: '', subject: '', html_content: '', text_content: '', category: '' }); }; const resetDripForm = () => { setDripForm({ name: '', description: '', trigger_event: '' }); }; const getStatusColor = (status: string) => { switch (status) { case 'sent': return 'bg-green-100 text-green-800'; case 'sending': return 'bg-blue-100 text-blue-800'; case 'scheduled': return 'bg-yellow-100 text-yellow-800'; case 'draft': return 'bg-gray-100 text-gray-800'; case 'paused': return 'bg-orange-100 text-orange-800'; default: return 'bg-gray-100 text-gray-800'; } }; if (loading && !campaigns.length && !segments.length && !templates.length) { return ; } return (
{/* Header */}

Email Marketing & Campaigns

Create, manage, and track email campaigns

{/* Tabs */}
{[ { id: 'campaigns', label: 'Campaigns', icon: Mail }, { id: 'segments', label: 'Segments', icon: Target }, { id: 'templates', label: 'Templates', icon: FileText }, { id: 'drip-sequences', label: 'Drip Campaigns', icon: Layers }, { id: 'analytics', label: 'Analytics', icon: BarChart3 } ].map(tab => { const Icon = tab.icon; return ( ); })}
{/* Content */}
{activeTab === 'campaigns' && (

Email Campaigns

{/* Filters */}
{/* Campaigns Table */}
{campaigns.map((campaign) => ( ))}
Name Type Status Recipients Open Rate Click Rate Date Actions
{campaign.name} {campaign.campaign_type} {campaign.status} {campaign.total_recipients} {campaign.open_rate !== null && campaign.open_rate !== undefined ? `${campaign.open_rate.toFixed(2)}%` : '-'} {campaign.click_rate !== null && campaign.click_rate !== undefined ? `${campaign.click_rate.toFixed(2)}%` : '-'} {campaign.sent_at ? formatDate(campaign.sent_at) : formatDate(campaign.created_at)}
{campaign.status === 'draft' && ( )}
)} {activeTab === 'segments' && ( setShowSegmentModal(true)} /> )} {activeTab === 'templates' && ( setShowTemplateModal(true)} /> )} {activeTab === 'drip-sequences' && ( { resetDripForm(); setShowDripModal(true); }} /> )} {activeTab === 'analytics' && analytics && ( )}
{/* Campaign Modal */} {showCampaignModal && ( { setShowCampaignModal(false); resetCampaignForm(); }} editing={!!editingItem} /> )} {/* Segment Modal */} {showSegmentModal && ( { setShowSegmentModal(false); resetSegmentForm(); }} /> )} {/* Template Modal */} {showTemplateModal && ( { setShowTemplateModal(false); resetTemplateForm(); }} /> )} {/* Drip Sequence Modal */} {showDripModal && ( { setShowDripModal(false); resetDripForm(); }} /> )}
); }; // Sub-components const SegmentsTab: React.FC<{ segments: CampaignSegment[]; onRefresh: () => void; onCreate: () => void; }> = ({ segments, onCreate }) => (

Segments

{segments.map((segment) => (

{segment.name}

{segment.description}

Estimated: {segment.estimated_count || 0} users

))}
); const TemplatesTab: React.FC<{ templates: EmailTemplate[]; onRefresh: () => void; onCreate: () => void; }> = ({ templates, onCreate }) => (

Email Templates

{templates.map((template) => (

{template.name}

{template.subject}

{template.category && ( {template.category} )}
))}
); const DripSequencesTab: React.FC<{ sequences: DripSequence[]; onRefresh: () => void; onCreate: () => void; }> = ({ sequences, onCreate }) => (

Drip Sequences

{sequences.map((sequence) => (

{sequence.name}

{sequence.description}

{sequence.step_count} steps {sequence.trigger_event && ` • Trigger: ${sequence.trigger_event}`}

))}
); const AnalyticsTab: React.FC<{ analytics: CampaignAnalytics }> = ({ analytics }) => (

Campaign Analytics

Open Rate

{analytics.open_rate.toFixed(2)}%

Click Rate

{analytics.click_rate.toFixed(2)}%

Total Opened

{analytics.total_opened}

Total Clicked

{analytics.total_clicked}

); const CampaignModal: React.FC<{ form: any; setForm: (form: any) => void; segments: CampaignSegment[]; templates: EmailTemplate[]; onSave: () => void; onClose: () => void; editing: boolean; }> = ({ form, setForm, segments, onSave, onClose, editing }) => (

{editing ? 'Edit Campaign' : 'Create Campaign'}

{editing ? 'Modify campaign details' : 'Create a new email campaign'}

setForm({ ...form, name: e.target.value })} className="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all duration-200 text-slate-700 font-medium shadow-sm" /> setForm({ ...form, subject: e.target.value })} className="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all duration-200 text-slate-700 font-medium shadow-sm" />