fix: campaign detail — cards above table layout (stacked, not side-by-side)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Campaign Details, Conversion Funnel, Source Breakdown now render as
3-column horizontal cards above the leads table. Table gets full width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 16:58:17 +05:30
parent 911ea4cd6c
commit 4ddad7c060

View File

@@ -73,108 +73,77 @@ export const CampaignDetailPage = () => {
<KpiStrip campaign={campaign} /> <KpiStrip campaign={campaign} />
{/* Main body: leads table on the left, campaign details + funnel + source on the right */} {/* Campaign details + funnel + source — horizontal cards above table */}
<div className="px-7 pt-5 pb-7"> <div className="px-7 pt-5">
<div className="grid grid-cols-1 gap-5 xl:grid-cols-[1fr_340px]"> <div className="grid grid-cols-1 gap-4 md:grid-cols-3 mb-6">
<div className="space-y-6"> <div className="rounded-xl border border-secondary bg-primary p-4">
<div> <h4 className="mb-3 text-sm font-bold text-primary">Campaign Details</h4>
<div className="mb-3 flex items-center justify-between"> <dl className="space-y-1.5 text-xs">
<h3 className="text-md font-bold text-primary"> {[
Leads ({campaignLeads.length}) ['Type', campaign.campaignType?.replace(/_/g, ' ') ?? '--'],
</h3> ['Platform', campaign.platform ?? '--'],
</div> ['Start', formatDateShort(campaign.startDate)],
{campaignLeads.length === 0 ? ( ['End', formatDateShort(campaign.endDate)],
<div className="rounded-xl border border-secondary bg-primary p-8 text-center text-sm text-tertiary"> ['Budget', campaign.budget ? formatCurrency(campaign.budget.amountMicros, campaign.budget.currencyCode) : '--'],
No leads from this campaign yet. ['Impressions', campaign.impressionCount?.toLocaleString('en-IN') ?? '--'],
['Clicks', campaign.clickCount?.toLocaleString('en-IN') ?? '--'],
].map(([label, value]) => (
<div key={label} className="flex justify-between">
<dt className="text-quaternary">{label}</dt>
<dd className="font-medium text-secondary">{value}</dd>
</div> </div>
) : ( ))}
<LeadTable </dl>
leads={sortedLeads} <div className="mt-3 space-y-2">
selectedIds={selectedIds} <BudgetBar spent={campaign.amountSpent} budget={campaign.budget} />
onSelectionChange={setSelectedIds} <HealthIndicator campaign={campaign} leads={campaignLeads} />
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
onViewActivity={(lead) => setActivityLead(lead)}
visibleColumns={new Set(['phone', 'name', 'source', 'status', 'lastContactedAt', 'createdAt'])}
/>
)}
</div> </div>
</div>
{campaignAds.length > 0 && ( <ConversionFunnel campaign={campaign} leads={campaignLeads} />
<div> <SourceBreakdown leads={campaignLeads} />
<h3 className="mb-3 text-md font-bold text-primary"> </div>
Ads ({campaignAds.length}) </div>
</h3>
<div className="space-y-3"> {/* Leads table — full width */}
{campaignAds.map((ad) => ( <div className="px-7 pb-7">
<AdCard key={ad.id} ad={ad} /> <div className="space-y-6">
))} <div>
</div> <div className="mb-3 flex items-center justify-between">
<h3 className="text-md font-bold text-primary">
Leads ({campaignLeads.length})
</h3>
</div>
{campaignLeads.length === 0 ? (
<div className="rounded-xl border border-secondary bg-primary p-8 text-center text-sm text-tertiary">
No leads from this campaign yet.
</div> </div>
) : (
<LeadTable
leads={sortedLeads}
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
onViewActivity={(lead) => setActivityLead(lead)}
visibleColumns={new Set(['phone', 'name', 'source', 'status', 'lastContactedAt', 'createdAt'])}
/>
)} )}
</div> </div>
<div className="space-y-4"> {campaignAds.length > 0 && (
<div className="rounded-xl border border-secondary bg-primary p-4"> <div>
<h4 className="mb-3 text-sm font-bold text-primary">Campaign Details</h4> <h3 className="mb-3 text-md font-bold text-primary">
<dl className="space-y-2 text-xs"> Ads ({campaignAds.length})
<div className="flex justify-between"> </h3>
<dt className="text-quaternary">Type</dt> <div className="space-y-3">
<dd className="font-medium text-secondary"> {campaignAds.map((ad) => (
{campaign.campaignType?.replace(/_/g, ' ') ?? '--'} <AdCard key={ad.id} ad={ad} />
</dd> ))}
</div>
<div className="flex justify-between">
<dt className="text-quaternary">Platform</dt>
<dd className="font-medium text-secondary">
{campaign.platform ?? '--'}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-quaternary">Start Date</dt>
<dd className="font-medium text-secondary">
{formatDateShort(campaign.startDate)}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-quaternary">End Date</dt>
<dd className="font-medium text-secondary">
{formatDateShort(campaign.endDate)}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-quaternary">Budget</dt>
<dd className="font-medium text-secondary">
{campaign.budget
? formatCurrency(campaign.budget.amountMicros, campaign.budget.currencyCode)
: '--'}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-quaternary">Impressions</dt>
<dd className="font-medium text-secondary">
{campaign.impressionCount?.toLocaleString('en-IN') ?? '--'}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-quaternary">Clicks</dt>
<dd className="font-medium text-secondary">
{campaign.clickCount?.toLocaleString('en-IN') ?? '--'}
</dd>
</div>
</dl>
<div className="mt-4 space-y-3">
<BudgetBar spent={campaign.amountSpent} budget={campaign.budget} />
<HealthIndicator campaign={campaign} leads={campaignLeads} />
</div> </div>
</div> </div>
)}
<ConversionFunnel campaign={campaign} leads={campaignLeads} />
<SourceBreakdown leads={campaignLeads} />
</div>
</div> </div>
</div> </div>