mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
fix: campaign detail — cards above table layout (stacked, not side-by-side)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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:
@@ -73,108 +73,77 @@ export const CampaignDetailPage = () => {
|
||||
|
||||
<KpiStrip campaign={campaign} />
|
||||
|
||||
{/* Main body: leads table on the left, campaign details + funnel + source on the right */}
|
||||
<div className="px-7 pt-5 pb-7">
|
||||
<div className="grid grid-cols-1 gap-5 xl:grid-cols-[1fr_340px]">
|
||||
<div className="space-y-6">
|
||||
<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.
|
||||
{/* Campaign details + funnel + source — horizontal cards above table */}
|
||||
<div className="px-7 pt-5">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3 mb-6">
|
||||
<div className="rounded-xl border border-secondary bg-primary p-4">
|
||||
<h4 className="mb-3 text-sm font-bold text-primary">Campaign Details</h4>
|
||||
<dl className="space-y-1.5 text-xs">
|
||||
{[
|
||||
['Type', campaign.campaignType?.replace(/_/g, ' ') ?? '--'],
|
||||
['Platform', campaign.platform ?? '--'],
|
||||
['Start', formatDateShort(campaign.startDate)],
|
||||
['End', formatDateShort(campaign.endDate)],
|
||||
['Budget', campaign.budget ? formatCurrency(campaign.budget.amountMicros, campaign.budget.currencyCode) : '--'],
|
||||
['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>
|
||||
) : (
|
||||
<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'])}
|
||||
/>
|
||||
)}
|
||||
))}
|
||||
</dl>
|
||||
<div className="mt-3 space-y-2">
|
||||
<BudgetBar spent={campaign.amountSpent} budget={campaign.budget} />
|
||||
<HealthIndicator campaign={campaign} leads={campaignLeads} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{campaignAds.length > 0 && (
|
||||
<div>
|
||||
<h3 className="mb-3 text-md font-bold text-primary">
|
||||
Ads ({campaignAds.length})
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{campaignAds.map((ad) => (
|
||||
<AdCard key={ad.id} ad={ad} />
|
||||
))}
|
||||
</div>
|
||||
<ConversionFunnel campaign={campaign} leads={campaignLeads} />
|
||||
<SourceBreakdown leads={campaignLeads} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Leads table — full width */}
|
||||
<div className="px-7 pb-7">
|
||||
<div className="space-y-6">
|
||||
<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>
|
||||
) : (
|
||||
<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 className="space-y-4">
|
||||
<div className="rounded-xl border border-secondary bg-primary p-4">
|
||||
<h4 className="mb-3 text-sm font-bold text-primary">Campaign Details</h4>
|
||||
<dl className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-quaternary">Type</dt>
|
||||
<dd className="font-medium text-secondary">
|
||||
{campaign.campaignType?.replace(/_/g, ' ') ?? '--'}
|
||||
</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} />
|
||||
{campaignAds.length > 0 && (
|
||||
<div>
|
||||
<h3 className="mb-3 text-md font-bold text-primary">
|
||||
Ads ({campaignAds.length})
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{campaignAds.map((ad) => (
|
||||
<AdCard key={ad.id} ad={ad} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConversionFunnel campaign={campaign} leads={campaignLeads} />
|
||||
|
||||
<SourceBreakdown leads={campaignLeads} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user