import { useState, useEffect, useContext } from 'react' import { useNavigate } from 'react-router-dom' import { Plus, Search, TrendingUp, DollarSign, Eye, MousePointer, Target, BarChart3 } from 'lucide-react' import { format } from 'date-fns' import { AppContext } from '../App' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../i18n/LanguageContext' import { api, PLATFORMS } from '../utils/api' import { PlatformIcons } from '../components/PlatformIcon' import StatusBadge from '../components/StatusBadge' import BrandBadge from '../components/BrandBadge' import Modal from '../components/Modal' import BudgetBar from '../components/BudgetBar' import InteractiveTimeline from '../components/InteractiveTimeline' const EMPTY_CAMPAIGN = { name: '', description: '', brand_id: '', status: 'planning', start_date: '', end_date: '', budget: '', goals: '', platforms: [], budget_spent: '', revenue: '', impressions: '', clicks: '', conversions: '', cost_per_click: '', notes: '', } function ROIBadge({ revenue, spent }) { if (!spent || spent <= 0) return null const roi = ((revenue - spent) / spent * 100).toFixed(0) const color = roi >= 0 ? 'text-emerald-600 bg-emerald-50' : 'text-red-600 bg-red-50' return ( ROI {roi}% ) } function MetricCard({ icon: Icon, label, value, color = 'text-text-primary' }) { return (
{value || '—'}
{label}
) } export default function Campaigns() { const { brands, getBrandName } = useContext(AppContext) const { lang } = useLanguage() const { permissions } = useAuth() const navigate = useNavigate() const [campaigns, setCampaigns] = useState([]) const [loading, setLoading] = useState(true) const [showModal, setShowModal] = useState(false) const [editingCampaign, setEditingCampaign] = useState(null) const [formData, setFormData] = useState(EMPTY_CAMPAIGN) const [filters, setFilters] = useState({ brand: '', status: '' }) const [activeTab, setActiveTab] = useState('details') // details | performance const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) useEffect(() => { loadCampaigns() }, []) const loadCampaigns = async () => { try { const res = await api.get('/campaigns') setCampaigns(res.data || res || []) } catch (err) { console.error('Failed to load campaigns:', err) } finally { setLoading(false) } } const handleSave = async () => { try { const data = { name: formData.name, description: formData.description, brand_id: formData.brand_id ? Number(formData.brand_id) : null, status: formData.status, start_date: formData.start_date, end_date: formData.end_date, budget: formData.budget ? Number(formData.budget) : null, goals: formData.goals, platforms: formData.platforms || [], budget_spent: formData.budget_spent ? Number(formData.budget_spent) : 0, revenue: formData.revenue ? Number(formData.revenue) : 0, impressions: formData.impressions ? Number(formData.impressions) : 0, clicks: formData.clicks ? Number(formData.clicks) : 0, conversions: formData.conversions ? Number(formData.conversions) : 0, cost_per_click: formData.cost_per_click ? Number(formData.cost_per_click) : 0, notes: formData.notes || '', } if (editingCampaign) { await api.patch(`/campaigns/${editingCampaign.id || editingCampaign._id}`, data) } else { await api.post('/campaigns', data) } setShowModal(false) setEditingCampaign(null) setFormData(EMPTY_CAMPAIGN) loadCampaigns() } catch (err) { console.error('Save failed:', err) } } const openEdit = (campaign) => { setEditingCampaign(campaign) setFormData({ name: campaign.name || '', description: campaign.description || '', brand_id: campaign.brandId || campaign.brand_id || '', status: campaign.status || 'planning', start_date: campaign.startDate ? new Date(campaign.startDate).toISOString().slice(0, 10) : '', end_date: campaign.endDate ? new Date(campaign.endDate).toISOString().slice(0, 10) : '', budget: campaign.budget || '', goals: campaign.goals || '', platforms: campaign.platforms || [], budget_spent: campaign.budgetSpent || campaign.budget_spent || '', revenue: campaign.revenue || '', impressions: campaign.impressions || '', clicks: campaign.clicks || '', conversions: campaign.conversions || '', cost_per_click: campaign.costPerClick || campaign.cost_per_click || '', notes: campaign.notes || '', }) setActiveTab('details') setShowModal(true) } const openNew = () => { setEditingCampaign(null) setFormData(EMPTY_CAMPAIGN) setActiveTab('details') setShowModal(true) } const filtered = campaigns.filter(c => { if (filters.brand && String(c.brandId || c.brand_id) !== filters.brand) return false if (filters.status && c.status !== filters.status) return false return true }) // Aggregate stats const totalBudget = filtered.reduce((sum, c) => sum + (c.budget || 0), 0) const totalSpent = filtered.reduce((sum, c) => sum + (c.budgetSpent || c.budget_spent || 0), 0) const totalImpressions = filtered.reduce((sum, c) => sum + (c.impressions || 0), 0) const totalClicks = filtered.reduce((sum, c) => sum + (c.clicks || 0), 0) const totalConversions = filtered.reduce((sum, c) => sum + (c.conversions || 0), 0) const totalRevenue = filtered.reduce((sum, c) => sum + (c.revenue || 0), 0) if (loading) { return (
) } return (
{/* Toolbar */}
{permissions?.canCreateCampaigns && ( )}
{/* Summary Cards */} {(totalBudget > 0 || totalSpent > 0) && (
Budget
{totalBudget.toLocaleString()}
SAR total
Spent
{totalSpent.toLocaleString()}
SAR spent
Impressions
{totalImpressions.toLocaleString()}
Clicks
{totalClicks.toLocaleString()}
Conversions
{totalConversions.toLocaleString()}
Revenue
{totalRevenue.toLocaleString()}
SAR
)} {/* Timeline */} ({ id: campaign._id || campaign.id, label: campaign.name, description: campaign.description, startDate: campaign.startDate || campaign.start_date || campaign.createdAt, endDate: campaign.endDate || campaign.end_date, status: campaign.status, assigneeName: campaign.brandName || campaign.brand_name, tags: campaign.platforms || [], })} onDateChange={async (campaignId, { startDate, endDate }) => { try { await api.patch(`/campaigns/${campaignId}`, { start_date: startDate, end_date: endDate }) } catch (err) { console.error('Timeline date update failed:', err) } finally { loadCampaigns() } }} onItemClick={(campaign) => { navigate(`/campaigns/${campaign._id || campaign.id}`) }} /> {/* Campaign list */}

All Campaigns

{filtered.length === 0 ? (
No campaigns found
) : ( filtered.map(campaign => { const spent = campaign.budgetSpent || campaign.budget_spent || 0 const budget = campaign.budget || 0 return (
navigate(`/campaigns/${campaign.id || campaign._id}`)} className="relative px-5 py-4 hover:bg-surface-secondary cursor-pointer transition-colors" >

{campaign.name}

{(campaign.brand_id || campaign.brandName) && }
{campaign.description && (

{campaign.description}

)}
{budget > 0 && (
)} {(campaign.impressions > 0 || campaign.clicks > 0) && (
{campaign.impressions > 0 && 👁 {campaign.impressions.toLocaleString()}} {campaign.clicks > 0 && 🖱 {campaign.clicks.toLocaleString()}} {campaign.conversions > 0 && 🎯 {campaign.conversions.toLocaleString()}}
)}
{campaign.startDate && campaign.endDate ? ( <> {format(new Date(campaign.startDate), 'MMM d')} – {format(new Date(campaign.endDate), 'MMM d, yyyy')} ) : '—'}
{campaign.platforms && campaign.platforms.length > 0 && (
)}
) }) )}
{/* Create/Edit Modal */} { setShowModal(false); setEditingCampaign(null) }} title={editingCampaign ? 'Edit Campaign' : 'Create Campaign'} size="lg" >
{/* Tabs */} {editingCampaign && (
)} {activeTab === 'details' ? ( <>
setFormData(f => ({ ...f, name: e.target.value }))} className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" placeholder="Campaign name" />