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, Pencil, Users, X, UserPlus, 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' 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'] const EMPTY_TRACK = { name: '', type: 'organic_social', platform: '', budget_allocated: '', status: 'planned', notes: '', } const EMPTY_METRICS = { budget_spent: '', revenue: '', impressions: '', clicks: '', conversions: '', notes: '', } function MetricBox({ label, value, icon: Icon, color = 'text-text-primary' }) { return (
{value ?? '—'}
{label}
) } export default function CampaignDetail() { const { id } = useParams() const navigate = useNavigate() const { brands, getBrandName } = useContext(AppContext) const { lang } = 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 [showTrackModal, setShowTrackModal] = useState(false) const [editingTrack, setEditingTrack] = useState(null) const [trackForm, setTrackForm] = useState(EMPTY_TRACK) const [showMetricsModal, setShowMetricsModal] = useState(false) const [metricsTrack, setMetricsTrack] = useState(null) const [metricsForm, setMetricsForm] = useState(EMPTY_METRICS) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [trackToDelete, setTrackToDelete] = useState(null) const [selectedPost, setSelectedPost] = useState(null) const [showDiscussion, setShowDiscussion] = useState(false) const [showEditModal, setShowEditModal] = useState(false) const [editForm, setEditForm] = useState({}) const [showDeleteCampaignConfirm, setShowDeleteCampaignConfirm] = 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]) 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.data || campRes || null) setTracks(tracksRes.data || tracksRes || []) setPosts(postsRes.data || postsRes || []) setAssignments(Array.isArray(assignRes) ? assignRes : (assignRes.data || [])) } 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 : (users.data || [])) } 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) } } const saveTrack = async () => { try { const data = { name: trackForm.name, type: trackForm.type, platform: trackForm.platform || null, budget_allocated: trackForm.budget_allocated ? Number(trackForm.budget_allocated) : 0, status: trackForm.status, notes: trackForm.notes, } if (editingTrack) { await api.patch(`/tracks/${editingTrack.id}`, data) } else { await api.post(`/campaigns/${id}/tracks`, data) } setShowTrackModal(false) setEditingTrack(null) setTrackForm(EMPTY_TRACK) loadAll() } catch (err) { console.error('Save track failed:', err) } } const deleteTrack = async (trackId) => { setTrackToDelete(trackId) setShowDeleteConfirm(true) } const confirmDeleteTrack = async () => { if (!trackToDelete) return await api.delete(`/tracks/${trackToDelete}`) setTrackToDelete(null) loadAll() } const saveMetrics = async () => { try { await api.patch(`/tracks/${metricsTrack.id}`, { budget_spent: metricsForm.budget_spent ? Number(metricsForm.budget_spent) : 0, revenue: metricsForm.revenue ? Number(metricsForm.revenue) : 0, impressions: metricsForm.impressions ? Number(metricsForm.impressions) : 0, clicks: metricsForm.clicks ? Number(metricsForm.clicks) : 0, conversions: metricsForm.conversions ? Number(metricsForm.conversions) : 0, notes: metricsForm.notes || '', }) setShowMetricsModal(false) setMetricsTrack(null) loadAll() } catch (err) { console.error('Save metrics failed:', err) } } const openEditTrack = (track) => { setEditingTrack(track) setTrackForm({ name: track.name || '', type: track.type || 'organic_social', platform: track.platform || '', budget_allocated: track.budget_allocated || '', status: track.status || 'planned', notes: track.notes || '', }) setShowTrackModal(true) } const openEditCampaign = () => { setEditForm({ name: campaign.name || '', description: campaign.description || '', status: campaign.status || 'planning', start_date: campaign.start_date ? new Date(campaign.start_date).toISOString().slice(0, 10) : '', end_date: campaign.end_date ? new Date(campaign.end_date).toISOString().slice(0, 10) : '', goals: campaign.goals || '', platforms: campaign.platforms || [], notes: campaign.notes || '', brand_id: campaign.brand_id || '', budget: campaign.budget || '', }) setShowEditModal(true) } const saveCampaignEdit = async () => { try { await api.patch(`/campaigns/${id}`, { name: editForm.name, description: editForm.description, status: editForm.status, start_date: editForm.start_date, end_date: editForm.end_date, goals: editForm.goals, platforms: editForm.platforms, notes: editForm.notes, brand_id: editForm.brand_id || null, budget: editForm.budget ? Number(editForm.budget) : null, }) setShowEditModal(false) loadAll() } catch (err) { console.error('Failed to update campaign:', err) } } const openMetrics = (track) => { setMetricsTrack(track) setMetricsForm({ budget_spent: track.budget_spent || '', revenue: track.revenue || '', impressions: track.impressions || '', clicks: track.clicks || '', conversions: track.conversions || '', notes: track.notes || '', }) setShowMetricsModal(true) } if (loading) { return
} if (!campaign) { return (
Campaign not found.
) } // 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')} )} Budget: {campaign.budget > 0 ? `${campaign.budget.toLocaleString()} SAR` : 'Not set'} {campaign.platforms && campaign.platforms.length > 0 && ( )}
{/* Action buttons */}
{canSetBudget && ( )} {canManage && ( )}
{/* Assigned Team */}

Assigned Team

{canAssign && ( )}
{assignments.length === 0 ? (

No team members assigned yet.

) : (
{assignments.map(a => (
{a.user_avatar ? ( ) : ( getInitials(a.user_name) )}
{a.user_name} {canAssign && ( )}
))}
)}
{/* Aggregate Metrics */} {tracks.length > 0 && (

Campaign Totals (from tracks)

{totalAllocated > 0 && (
)}
)} {/* Tracks */}

Tracks

{canManage && ( )}
{tracks.length === 0 ? (
No tracks yet. Add organic, paid, or SEO tracks to organize this campaign.
) : (
{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)} SAR )} {track.impressions > 0 && track.clicks > 0 && ( CTR: {(track.clicks / track.impressions * 100).toFixed(2)}% )}
)} {/* Linked posts count */} {trackPosts.length > 0 && (
📝 {trackPosts.length} post{trackPosts.length !== 1 ? 's' : ''} linked
)} {track.notes && (

{track.notes}

)}
{/* Actions */} {canManage && (
)}
) })}
)}
{/* Linked Posts */} {posts.length > 0 && (

Linked Posts ({posts.length})

{posts.map(post => (
setSelectedPost(post)} 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 && (

Discussion

)} {/* Add/Edit Track Modal */} { setShowTrackModal(false); setEditingTrack(null) }} title={editingTrack ? 'Edit Track' : 'Add Track'} >
setTrackForm(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="e.g., Instagram Paid Ads, Organic Wave, Google Search..." />
setTrackForm(f => ({ ...f, budget_allocated: e.target.value }))} className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none" placeholder="0 for free/organic" />