update on timeline on portfolio view + some corrections
This commit is contained in:
@@ -6,11 +6,11 @@ import { AppContext } from '../App'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { api, PLATFORMS } from '../utils/api'
|
||||
import { PlatformIcons } from '../components/PlatformIcon'
|
||||
import CampaignCalendar from '../components/CampaignCalendar'
|
||||
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',
|
||||
@@ -241,8 +241,32 @@ export default function Campaigns() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Calendar */}
|
||||
<CampaignCalendar campaigns={filtered} />
|
||||
{/* Timeline */}
|
||||
<InteractiveTimeline
|
||||
items={filtered}
|
||||
mapItem={(campaign) => ({
|
||||
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 */}
|
||||
<div className="bg-white rounded-xl border border-border overflow-hidden">
|
||||
@@ -261,47 +285,50 @@ export default function Campaigns() {
|
||||
return (
|
||||
<div
|
||||
key={campaign.id || campaign._id}
|
||||
onClick={() => permissions?.canEditCampaigns ? navigate(`/campaigns/${campaign.id || campaign._id}`) : navigate(`/campaigns/${campaign.id || campaign._id}`)}
|
||||
className="flex items-center gap-4 px-5 py-4 hover:bg-surface-secondary cursor-pointer transition-colors"
|
||||
onClick={() => navigate(`/campaigns/${campaign.id || campaign._id}`)}
|
||||
className="relative px-5 py-4 hover:bg-surface-secondary cursor-pointer transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="text-sm font-semibold text-text-primary">{campaign.name}</h4>
|
||||
{campaign.brandName && <BrandBadge brand={campaign.brandName} />}
|
||||
<ROIBadge revenue={campaign.revenue || 0} spent={spent} />
|
||||
</div>
|
||||
{campaign.description && (
|
||||
<p className="text-xs text-text-secondary line-clamp-1">{campaign.description}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mt-1.5">
|
||||
{campaign.platforms && campaign.platforms.length > 0 && (
|
||||
<PlatformIcons platforms={campaign.platforms} size={16} />
|
||||
)}
|
||||
{budget > 0 && (
|
||||
<div className="w-32">
|
||||
<BudgetBar budget={budget} spent={spent} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Quick metrics row */}
|
||||
{(campaign.impressions > 0 || campaign.clicks > 0) && (
|
||||
<div className="flex items-center gap-3 mt-1.5 text-[10px] text-text-tertiary">
|
||||
{campaign.impressions > 0 && <span>👁 {campaign.impressions.toLocaleString()}</span>}
|
||||
{campaign.clicks > 0 && <span>🖱 {campaign.clicks.toLocaleString()}</span>}
|
||||
{campaign.conversions > 0 && <span>🎯 {campaign.conversions.toLocaleString()}</span>}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="text-sm font-semibold text-text-primary">{campaign.name}</h4>
|
||||
{campaign.brandName && <BrandBadge brand={campaign.brandName} />}
|
||||
<ROIBadge revenue={campaign.revenue || 0} spent={spent} />
|
||||
</div>
|
||||
{campaign.description && (
|
||||
<p className="text-xs text-text-secondary line-clamp-1">{campaign.description}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mt-1.5">
|
||||
{budget > 0 && (
|
||||
<div className="w-32">
|
||||
<BudgetBar budget={budget} spent={spent} />
|
||||
</div>
|
||||
)}
|
||||
{(campaign.impressions > 0 || campaign.clicks > 0) && (
|
||||
<div className="flex items-center gap-3 text-[10px] text-text-tertiary">
|
||||
{campaign.impressions > 0 && <span>👁 {campaign.impressions.toLocaleString()}</span>}
|
||||
{campaign.clicks > 0 && <span>🖱 {campaign.clicks.toLocaleString()}</span>}
|
||||
{campaign.conversions > 0 && <span>🎯 {campaign.conversions.toLocaleString()}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<StatusBadge status={campaign.status} size="xs" />
|
||||
<div className="text-xs text-text-tertiary mt-1">
|
||||
{campaign.startDate && campaign.endDate ? (
|
||||
<>
|
||||
{format(new Date(campaign.startDate), 'MMM d')} – {format(new Date(campaign.endDate), 'MMM d, yyyy')}
|
||||
</>
|
||||
) : '—'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<StatusBadge status={campaign.status} size="xs" />
|
||||
<div className="text-xs text-text-tertiary mt-1">
|
||||
{campaign.startDate && campaign.endDate ? (
|
||||
<>
|
||||
{format(new Date(campaign.startDate), 'MMM d')} – {format(new Date(campaign.endDate), 'MMM d, yyyy')}
|
||||
</>
|
||||
) : '—'}
|
||||
</div>
|
||||
</div>
|
||||
{campaign.platforms && campaign.platforms.length > 0 && (
|
||||
<div className="flex justify-end mt-2">
|
||||
<PlatformIcons platforms={campaign.platforms} size={16} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user