import { useState, useEffect, useContext } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { ArrowLeft, Plus, Edit2, Trash2, DollarSign, Eye, MousePointer, Target, TrendingUp, FileText, Megaphone, Search, Globe, Users, X, MessageCircle, Settings } 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, getInitials } from '../utils/api' import PlatformIcon, { 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 CommentsSection from '../components/CommentsSection' import CampaignDetailPanel from '../components/CampaignDetailPanel' import TrackDetailPanel from '../components/TrackDetailPanel' const TRACK_TYPES = { organic_social: { label: 'Organic Social', icon: Megaphone, color: 'text-green-600 bg-green-50', hasBudget: false }, paid_social: { label: 'Paid Social', icon: DollarSign, color: 'text-blue-600 bg-blue-50', hasBudget: true }, paid_search: { label: 'Paid Search (PPC)', icon: Search, color: 'text-amber-600 bg-amber-50', hasBudget: true }, seo_content: { label: 'SEO / Content', icon: Globe, color: 'text-purple-600 bg-purple-50', hasBudget: false }, production: { label: 'Production', icon: FileText, color: 'text-red-600 bg-red-50', hasBudget: true }, } const TRACK_STATUSES = ['planned', 'active', 'paused', 'completed'] export default function CampaignDetail() { const { id } = useParams() const navigate = useNavigate() const { brands, getBrandName, teamMembers } = useContext(AppContext) const { t, lang, currencySymbol } = useLanguage() const { permissions, user } = useAuth() const isSuperadmin = user?.role === 'superadmin' const [campaign, setCampaign] = useState(null) const [tracks, setTracks] = useState([]) const [posts, setPosts] = useState([]) const [assignments, setAssignments] = useState([]) const [allUsers, setAllUsers] = useState([]) const [loading, setLoading] = useState(true) const [showAssignModal, setShowAssignModal] = useState(false) const [selectedUserIds, setSelectedUserIds] = useState([]) const canSetBudget = permissions?.canSetBudget const [editingBudget, setEditingBudget] = useState(false) const [budgetValue, setBudgetValue] = useState('') const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [trackToDelete, setTrackToDelete] = useState(null) const [showDiscussion, setShowDiscussion] = useState(false) const [allCampaigns, setAllCampaigns] = useState([]) // Panel state const [panelCampaign, setPanelCampaign] = useState(null) const [panelTrack, setPanelTrack] = useState(null) const [trackScrollToMetrics, setTrackScrollToMetrics] = useState(false) const isCreator = campaign?.createdByUserId === user?.id || campaign?.created_by_user_id === user?.id const canManage = isSuperadmin || (permissions?.canEditCampaigns && isCreator) const canAssign = isSuperadmin || (permissions?.canAssignCampaigns && isCreator) useEffect(() => { loadAll() }, [id]) useEffect(() => { api.get('/campaigns').then(r => setAllCampaigns(Array.isArray(r) ? r : [])).catch(() => {}) }, []) const loadAll = async () => { try { const [campRes, tracksRes, postsRes, assignRes] = await Promise.all([ api.get(`/campaigns/${id}`), api.get(`/campaigns/${id}/tracks`), api.get(`/campaigns/${id}/posts`), api.get(`/campaigns/${id}/assignments`), ]) setCampaign(campRes) setTracks(Array.isArray(tracksRes) ? tracksRes : []) setPosts(Array.isArray(postsRes) ? postsRes : []) setAssignments(Array.isArray(assignRes) ? assignRes : []) } catch (err) { console.error('Failed to load campaign:', err) } finally { setLoading(false) } } const loadUsersForAssign = async () => { try { const users = await api.get('/users/team?all=true') setAllUsers(Array.isArray(users) ? users : []) } catch (err) { console.error('Failed to load users:', err) } } const openAssignModal = () => { loadUsersForAssign() setSelectedUserIds(assignments.map(a => a.user_id)) setShowAssignModal(true) } const saveAssignments = async () => { try { const currentIds = assignments.map(a => a.user_id) const toAdd = selectedUserIds.filter(id => !currentIds.includes(id)) const toRemove = currentIds.filter(id => !selectedUserIds.includes(id)) if (toAdd.length > 0) { await api.post(`/campaigns/${id}/assignments`, { user_ids: toAdd }) } for (const uid of toRemove) { await api.delete(`/campaigns/${id}/assignments/${uid}`) } setShowAssignModal(false) loadAll() } catch (err) { console.error('Failed to save assignments:', err) } } const removeAssignment = async (userId) => { try { await api.delete(`/campaigns/${id}/assignments/${userId}`) loadAll() } catch (err) { console.error('Failed to remove assignment:', err) } } // Panel handlers const handleCampaignPanelSave = async (campaignId, data) => { await api.patch(`/campaigns/${campaignId}`, data) loadAll() } const handleCampaignPanelDelete = async (campaignId) => { await api.delete(`/campaigns/${campaignId}`) navigate('/campaigns') } const handleTrackPanelSave = async (trackId, data) => { if (trackId) { await api.patch(`/tracks/${trackId}`, data) } else { await api.post(`/campaigns/${id}/tracks`, data) } setPanelTrack(null) loadAll() } const handleTrackPanelDelete = async (trackId) => { await api.delete(`/tracks/${trackId}`) setPanelTrack(null) loadAll() } const deleteTrack = async (trackId) => { setTrackToDelete(trackId) setShowDeleteConfirm(true) } const confirmDeleteTrack = async () => { if (!trackToDelete) return await api.delete(`/tracks/${trackToDelete}`) setTrackToDelete(null) loadAll() } if (loading) { return (
) } if (!campaign) { return (
{t('campaigns.notFound')}
) } // Aggregates from tracks const totalAllocated = tracks.reduce((s, t) => s + (t.budget_allocated || 0), 0) const totalSpent = tracks.reduce((s, t) => s + (t.budget_spent || 0), 0) const totalImpressions = tracks.reduce((s, t) => s + (t.impressions || 0), 0) const totalClicks = tracks.reduce((s, t) => s + (t.clicks || 0), 0) const totalConversions = tracks.reduce((s, t) => s + (t.conversions || 0), 0) const totalRevenue = tracks.reduce((s, t) => s + (t.revenue || 0), 0) return (
{/* Main content */}
{/* Header */}

{campaign.name}

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

{campaign.description}

}
{campaign.start_date && campaign.end_date && ( {format(new Date(campaign.start_date), 'MMM d')} – {format(new Date(campaign.end_date), 'MMM d, yyyy')} )} {campaign.platforms && campaign.platforms.length > 0 && ( )}
{/* Action buttons */}
{canManage && ( )}
{/* Budget Card */}

{t('campaigns.budget')}

{canSetBudget && ( )}
{totalAllocated.toLocaleString()} {currencySymbol} {t('finance.allocated')}
{totalAllocated > 0 && ( <>
{totalSpent.toLocaleString()} {currencySymbol} {t('dashboard.spent')} {(totalAllocated - totalSpent).toLocaleString()} {currencySymbol} {t('dashboard.remaining')}
)} {(totalImpressions > 0 || totalClicks > 0) && (
{totalImpressions.toLocaleString()} {totalClicks.toLocaleString()} {totalConversions > 0 && {totalConversions.toLocaleString()}} {totalRevenue > 0 && {totalRevenue.toLocaleString()} {currencySymbol}}
)}
{/* Tracks */}

{t('campaigns.tracks')}

{canManage && ( )}
{tracks.length === 0 ? (
{t('campaigns.noTracks')}
) : (
{tracks.map(track => { const typeInfo = TRACK_TYPES[track.type] || TRACK_TYPES.organic_social const TypeIcon = typeInfo.icon const trackPosts = posts.filter(p => p.track_id === track.id) return (

{track.name || typeInfo.label}

{typeInfo.label} {track.platform && ( )}
{/* Budget bar for paid tracks */} {track.budget_allocated > 0 && (
)} {/* Quick metrics */} {(track.impressions > 0 || track.clicks > 0 || track.conversions > 0) && (
{track.impressions > 0 && {track.impressions.toLocaleString()}} {track.clicks > 0 && {track.clicks.toLocaleString()}} {track.conversions > 0 && {track.conversions.toLocaleString()}} {track.clicks > 0 && track.budget_spent > 0 && ( CPC: {(track.budget_spent / track.clicks).toFixed(2)} {currencySymbol} )} {track.impressions > 0 && track.clicks > 0 && ( CTR: {(track.clicks / track.impressions * 100).toFixed(2)}% )}
)} {/* Linked posts count */} {trackPosts.length > 0 && (
{trackPosts.length} {t('campaigns.postsLinked')}
)} {track.notes && (

{track.notes}

)}
{/* Actions */} {canManage && (
)}
) })}
)}
{/* Team */} {(assignments.length > 0 || canAssign) && (
{t('campaigns.team')}:
{assignments.slice(0, 6).map(a => (
{a.user_avatar ? : getInitials(a.user_name)}
))} {assignments.length > 6 &&
+{assignments.length - 6}
}
{canAssign && ( )}
)} {/* Linked Posts */} {posts.length > 0 && (

{t('campaigns.linkedPosts')} ({posts.length})

{posts.map(post => (
navigate(`/posts/${post._id || post.id || post.Id}`)} className="flex items-center gap-3 px-5 py-3 hover:bg-surface-secondary cursor-pointer transition-colors" > {post.thumbnail_url && ( )}

{post.title}

{post.track_name && {post.track_name}} {post.brand_name && } {post.assigned_name && → {post.assigned_name}} {post.platforms && post.platforms.length > 0 && ( )}
))}
)}
{/* end main content */} {/* ─── DISCUSSION SIDEBAR ─── */} {showDiscussion && (

{t('campaigns.discussion')}

)} {/* Delete Track Confirmation */} { setShowDeleteConfirm(false); setTrackToDelete(null) }} title="Delete Track?" isConfirm danger confirmText="Delete Track" onConfirm={confirmDeleteTrack} > Are you sure you want to delete this campaign track? This action cannot be undone. {/* Assign Members Modal */} setShowAssignModal(false)} title="Assign Team Members" >
{allUsers.map(u => { const checked = selectedUserIds.includes(u.id || u._id) return ( ) })}
{/* Budget Modal */} setEditingBudget(false)} title="Set Campaign Budget" size="sm">
setBudgetValue(e.target.value)} autoFocus min="0" 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="Enter budget amount" />
{/* Campaign Edit Panel */} {panelCampaign && ( setPanelCampaign(null)} onSave={handleCampaignPanelSave} onDelete={permissions?.canDeleteCampaigns ? handleCampaignPanelDelete : null} brands={brands} permissions={permissions} /> )} {/* Track Detail Panel */} {panelTrack && ( setPanelTrack(null)} onSave={handleTrackPanelSave} onDelete={handleTrackPanelDelete} scrollToMetrics={trackScrollToMetrics} /> )}
) }