update on timeline on portfolio view + some corrections

This commit is contained in:
fahed
2026-02-10 13:20:49 +03:00
parent d15e54044e
commit 334727b232
37 changed files with 5119 additions and 1440 deletions

View File

@@ -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>
)
})