fix: split Google Ads creatives by Search / PMax / Display
Fetch display/RDA assets, classify Brand vs Generic Search, and group the Ad Tracker gallery so previews are not a single undifferentiated image. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -148,6 +148,72 @@ export function CreativesGallery({
|
||||
? warning
|
||||
: null);
|
||||
|
||||
const googleGroups: {
|
||||
key: string;
|
||||
title: string;
|
||||
hint: string;
|
||||
items: AdCreativeCard[];
|
||||
}[] =
|
||||
platform === 'google'
|
||||
? [
|
||||
{
|
||||
key: 'search_brand',
|
||||
title: 'Search · Brand',
|
||||
hint: 'RSA de marca (titulares + URL)',
|
||||
items: filtered.filter((a) => a.channel === 'search_brand'),
|
||||
},
|
||||
{
|
||||
key: 'search_generic',
|
||||
title: 'Search · Generic',
|
||||
hint: 'RSA genéricos / captación',
|
||||
items: filtered.filter(
|
||||
(a) =>
|
||||
a.channel === 'search_generic' ||
|
||||
(!a.channel && a.mediaType === 'rsa')
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'pmax',
|
||||
title: 'Performance Max',
|
||||
hint: 'Asset groups · imágenes, logos y vídeo',
|
||||
items: filtered.filter(
|
||||
(a) => a.channel === 'pmax' || a.mediaType === 'pmax'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'display',
|
||||
title: 'Display · Remarketing',
|
||||
hint: 'RDA / image ads · creatividades visuales',
|
||||
items: filtered.filter(
|
||||
(a) =>
|
||||
a.channel === 'display' ||
|
||||
a.mediaType === 'rda' ||
|
||||
a.mediaType === 'image'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'other',
|
||||
title: 'Otros Google',
|
||||
hint: 'Campañas sin clasificar',
|
||||
items: filtered.filter((a) => {
|
||||
const ch = a.channel;
|
||||
if (
|
||||
ch === 'search_brand' ||
|
||||
ch === 'search_generic' ||
|
||||
ch === 'pmax' ||
|
||||
ch === 'display'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (!ch && (a.mediaType === 'rsa' || a.mediaType === 'pmax' || a.mediaType === 'rda' || a.mediaType === 'image')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
},
|
||||
].filter((g) => g.items.length > 0)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
@@ -158,7 +224,9 @@ export function CreativesGallery({
|
||||
{platform === 'meta' ? 'Meta Ads' : 'Google Ads'} · creatividades
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Preview 9:16. Pulsa Zoom para ver el resto de piezas en su formato.
|
||||
{platform === 'google'
|
||||
? 'Agrupado por Search Brand / Generic, Performance Max y Display. Search muestra preview tipo SERP; PMax y Display muestran assets.'
|
||||
: 'Preview 9:16. Pulsa Zoom para ver el resto de piezas en su formato.'}
|
||||
</p>
|
||||
</div>
|
||||
{platformWarning ? (
|
||||
@@ -174,6 +242,26 @@ export function CreativesGallery({
|
||||
<p className="text-sm text-gray-500 py-8 text-center border border-dashed border-gray-200 rounded-xl">
|
||||
Sin creatividades para este periodo.
|
||||
</p>
|
||||
) : platform === 'google' && googleGroups.length > 0 ? (
|
||||
<div className="space-y-8">
|
||||
{googleGroups.map((g) => (
|
||||
<div key={g.key} className="space-y-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-gray-900">{g.title}</h4>
|
||||
<p className="text-[11px] text-gray-500">{g.hint} · {g.items.length}</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{g.items.map((ad) => (
|
||||
<CreativeCard
|
||||
key={`${ad.platform}-${ad.adId}`}
|
||||
ad={ad}
|
||||
onZoom={() => onZoom(ad)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{filtered.map((ad) => (
|
||||
@@ -219,10 +307,41 @@ function CreativeCard({
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : ad.platform === 'google' &&
|
||||
(ad.mediaType === 'rsa' ||
|
||||
ad.channel === 'search_brand' ||
|
||||
ad.channel === 'search_generic') &&
|
||||
headlines.length ? (
|
||||
<div className="w-full h-full p-3.5 space-y-1.5 text-left bg-white flex flex-col justify-center border-t border-gray-100">
|
||||
<p className="text-[9px] uppercase tracking-wider text-emerald-700 font-medium">
|
||||
Anuncio ·{' '}
|
||||
{ad.channel === 'search_brand'
|
||||
? 'Search Brand'
|
||||
: ad.channel === 'search_generic'
|
||||
? 'Search Generic'
|
||||
: 'Search'}
|
||||
</p>
|
||||
<p className="text-[10px] text-emerald-800 truncate">
|
||||
{(ad.linkUrl || 'https://ejemplo.com').replace(/^https?:\/\//, '')}
|
||||
</p>
|
||||
{headlines.slice(0, 2).map((h) => (
|
||||
<p
|
||||
key={h}
|
||||
className="text-[13px] font-medium text-[#1a0dab] leading-snug line-clamp-2"
|
||||
>
|
||||
{h}
|
||||
</p>
|
||||
))}
|
||||
{bodies[0] ? (
|
||||
<p className="text-[11px] text-gray-600 leading-snug line-clamp-3">
|
||||
{bodies[0]}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : ad.platform === 'google' && headlines.length ? (
|
||||
<div className="w-full h-full p-4 space-y-2 text-left bg-gradient-to-b from-[#1a2332] to-[#0d121f] flex flex-col justify-center">
|
||||
<p className="text-[10px] uppercase tracking-wider text-sky-400">
|
||||
Google · {ad.mediaType || 'rsa'}
|
||||
Google · {ad.mediaType || 'ad'}
|
||||
</p>
|
||||
{headlines.slice(0, 3).map((h) => (
|
||||
<p
|
||||
|
||||
Reference in New Issue
Block a user