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 BudgetBar from '../components/BudgetBar' import InteractiveTimeline from '../components/InteractiveTimeline' import CampaignDetailPanel from '../components/CampaignDetailPanel' import Modal from '../components/Modal' import { SkeletonStatCard, SkeletonTable } from '../components/SkeletonLoader' const EMPTY_CAMPAIGN = { name: '', description: '', brand_id: '', status: 'planning', start_date: '', end_date: '', budget: '', team_id: '', } 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, teams } = useContext(AppContext) const { t, lang, currencySymbol } = useLanguage() const { permissions } = useAuth() const navigate = useNavigate() const [campaigns, setCampaigns] = useState([]) const [loading, setLoading] = useState(true) const [panelCampaign, setPanelCampaign] = useState(null) const [filters, setFilters] = useState({ brand: '', status: '' }) const [showCreateModal, setShowCreateModal] = useState(false) const [createForm, setCreateForm] = useState({ ...EMPTY_CAMPAIGN }) const [createSaving, setCreateSaving] = useState(false) useEffect(() => { loadCampaigns() }, []) const loadCampaigns = async () => { try { const res = await api.get('/campaigns') setCampaigns(Array.isArray(res) ? res : []) } catch (err) { console.error('Failed to load campaigns:', err) } finally { setLoading(false) } } const handlePanelSave = async (campaignId, data) => { if (campaignId) { await api.patch(`/campaigns/${campaignId}`, data) } else { await api.post('/campaigns', data) } loadCampaigns() } const handlePanelDelete = async (campaignId) => { await api.delete(`/campaigns/${campaignId}`) loadCampaigns() } const openNew = () => { setCreateForm({ ...EMPTY_CAMPAIGN }) setShowCreateModal(true) } const handleCreate = async () => { setCreateSaving(true) try { const data = { name: createForm.name, description: createForm.description, brand_id: createForm.brand_id ? Number(createForm.brand_id) : null, status: createForm.status, start_date: createForm.start_date || null, end_date: createForm.end_date || null, budget: createForm.budget ? Number(createForm.budget) : null, team_id: createForm.team_id ? Number(createForm.team_id) : null, } const created = await api.post('/campaigns', data) setShowCreateModal(false) loadCampaigns() // Navigate to the new campaign detail page const id = created?.Id || created?.id || created?._id if (id) navigate(`/campaigns/${id}`) } catch (err) { console.error('Create campaign failed:', err) } finally { setCreateSaving(false) } } 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 (
{[...Array(6)].map((_, i) => )}
) } return (
{/* Toolbar */}
{permissions?.canCreateCampaigns && ( )}
{/* Summary Cards */} {(totalBudget > 0 || totalSpent > 0) && (
Budget
{totalBudget.toLocaleString()}
{currencySymbol} total
Spent
{totalSpent.toLocaleString()}
{currencySymbol} spent
Impressions
{totalImpressions.toLocaleString()}
Clicks
{totalClicks.toLocaleString()}
Conversions
{totalConversions.toLocaleString()}
Revenue
{totalRevenue.toLocaleString()}
{currencySymbol}
)} {/* 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 || [], color: campaign.color, })} 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() } }} onColorChange={async (campaignId, color) => { try { await api.patch(`/campaigns/${campaignId}`, { color: color || '' }) } catch (err) { console.error('Color 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 Campaign Modal */} setShowCreateModal(false)} title={t('campaigns.newCampaign') || 'New Campaign'} size="md">
setCreateForm(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" autoFocus />