fix: Social — métricas IG total_value, posts en formato original y tamaño
Rellena views/perfil/clics/interacciones con metric_type=total_value y series día a día; carruseles/vídeos/reels con aspect ratio y px originales. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -85,13 +85,147 @@ type SocialPost = {
|
||||
reach?: number | null;
|
||||
impressions?: number | null;
|
||||
mediaType?: string | null;
|
||||
mediaProductType?: string | null;
|
||||
children?: {
|
||||
id?: string;
|
||||
mediaType?: string | null;
|
||||
mediaUrl?: string | null;
|
||||
thumbnailUrl?: string | null;
|
||||
}[];
|
||||
};
|
||||
|
||||
function isVideoPost(p: SocialPost): boolean {
|
||||
function formatLabel(p: SocialPost): string {
|
||||
const t = (p.mediaType || '').toUpperCase();
|
||||
const prod = (p.mediaProductType || '').toUpperCase();
|
||||
if (prod === 'REELS' || t === 'REELS' || t === 'REEL') return 'Reel';
|
||||
if (t.includes('CAROUSEL') || t.includes('ALBUM')) return 'Carrusel';
|
||||
if (t.includes('VIDEO')) return 'Vídeo';
|
||||
if (t.includes('IMAGE') || t === 'PHOTO') return 'Imagen';
|
||||
if (t === 'STORY' || prod === 'STORY') return 'Story';
|
||||
return t || 'Post';
|
||||
}
|
||||
|
||||
function isVideoSlide(mediaType?: string | null, url?: string | null): boolean {
|
||||
const t = (mediaType || '').toUpperCase();
|
||||
if (t.includes('VIDEO') || t === 'REELS' || t === 'REEL') return true;
|
||||
const url = p.mediaUrl || '';
|
||||
return /\.mp4($|\?)/i.test(url) || /video/i.test(url);
|
||||
return /\.mp4($|\?)/i.test(url || '') || /video/i.test(url || '');
|
||||
}
|
||||
|
||||
function proxiedSocialUrl(raw?: string | null): string {
|
||||
if (!raw) return '';
|
||||
if (raw.startsWith('/api/')) return raw;
|
||||
return `/api/ad-tracker/media?url=${encodeURIComponent(raw)}`;
|
||||
}
|
||||
|
||||
function PostMedia({ post }: { post: SocialPost }) {
|
||||
const slides =
|
||||
post.children && post.children.length > 0
|
||||
? post.children
|
||||
: [
|
||||
{
|
||||
id: post.id,
|
||||
mediaType: post.mediaType,
|
||||
mediaUrl: post.mediaUrl,
|
||||
thumbnailUrl: post.thumbnailUrl,
|
||||
},
|
||||
];
|
||||
const [idx, setIdx] = useState(0);
|
||||
const [dims, setDims] = useState<{ w: number; h: number } | null>(null);
|
||||
const slide = slides[Math.min(idx, slides.length - 1)] || slides[0];
|
||||
const video = isVideoSlide(slide?.mediaType, slide?.mediaUrl);
|
||||
const src = proxiedSocialUrl(slide?.mediaUrl || slide?.thumbnailUrl);
|
||||
const poster = proxiedSocialUrl(slide?.thumbnailUrl);
|
||||
const aspect =
|
||||
dims && dims.w > 0 && dims.h > 0 ? `${dims.w} / ${dims.h}` : video ? '9 / 16' : '1 / 1';
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div
|
||||
className="relative w-full bg-gray-950 overflow-hidden rounded-t-2xl"
|
||||
style={{ aspectRatio: aspect, maxHeight: 520 }}
|
||||
>
|
||||
{src && video ? (
|
||||
<video
|
||||
key={src}
|
||||
src={src}
|
||||
poster={poster || undefined}
|
||||
className="absolute inset-0 w-full h-full object-contain bg-black"
|
||||
controls
|
||||
playsInline
|
||||
preload="metadata"
|
||||
onLoadedMetadata={(e) => {
|
||||
const el = e.currentTarget;
|
||||
if (el.videoWidth && el.videoHeight) {
|
||||
setDims({ w: el.videoWidth, h: el.videoHeight });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : src ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
key={src}
|
||||
src={src}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-contain bg-black"
|
||||
onLoad={(e) => {
|
||||
const el = e.currentTarget;
|
||||
if (el.naturalWidth && el.naturalHeight) {
|
||||
setDims({ w: el.naturalWidth, h: el.naturalHeight });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center text-xs text-gray-400 px-4 text-center">
|
||||
{post.message?.slice(0, 120) || 'Sin media'}
|
||||
</div>
|
||||
)}
|
||||
<span className="absolute top-2 left-2 text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-black/70 text-white">
|
||||
{post.platform === 'instagram' ? 'IG' : 'FB'} · {formatLabel(post)}
|
||||
{slides.length > 1 ? ` · ${idx + 1}/${slides.length}` : ''}
|
||||
</span>
|
||||
{dims ? (
|
||||
<span className="absolute top-2 right-2 text-[10px] font-semibold tabular-nums px-2 py-0.5 rounded bg-black/70 text-white">
|
||||
{dims.w}×{dims.h}
|
||||
{dims.w && dims.h
|
||||
? ` · ${(dims.w / dims.h).toFixed(2)}:1`
|
||||
: ''}
|
||||
</span>
|
||||
) : null}
|
||||
{slides.length > 1 ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Anterior"
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-black/60 text-white text-sm"
|
||||
onClick={() => {
|
||||
setDims(null);
|
||||
setIdx((i) => (i - 1 + slides.length) % slides.length);
|
||||
}}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Siguiente"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-black/60 text-white text-sm"
|
||||
onClick={() => {
|
||||
setDims(null);
|
||||
setIdx((i) => (i + 1) % slides.length);
|
||||
}}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{dims ? (
|
||||
<p className="px-3 text-[10px] text-gray-500 tabular-nums">
|
||||
Tamaño original: {dims.w} × {dims.h}px
|
||||
{video ? ' (vídeo)' : slides.length > 1 ? ` · slide ${idx + 1}` : ''}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const CHANNELS: {
|
||||
@@ -263,7 +397,7 @@ export default function SocialDashboard({
|
||||
icon: Eye,
|
||||
},
|
||||
{
|
||||
label: 'Impresiones/vistas IG',
|
||||
label: 'Visualizaciones IG',
|
||||
value: n(ig?.igInsights?.impressions),
|
||||
icon: Eye,
|
||||
},
|
||||
@@ -324,8 +458,8 @@ export default function SocialDashboard({
|
||||
color: 'bg-pink-400/80',
|
||||
},
|
||||
{
|
||||
title: 'IG · Impresiones/vistas',
|
||||
hint: 'Views / impressions',
|
||||
title: 'IG · Visualizaciones',
|
||||
hint: 'views (metric_type=total_value)',
|
||||
series: ig?.series?.impressions,
|
||||
total: ig?.igInsights?.impressions,
|
||||
color: 'bg-fuchsia-400/80',
|
||||
@@ -524,36 +658,13 @@ export default function SocialDashboard({
|
||||
key={p.id}
|
||||
className="rounded-2xl border border-gray-200 bg-white overflow-hidden flex flex-col"
|
||||
>
|
||||
<div className="relative aspect-square bg-gray-100">
|
||||
{p.mediaUrl && isVideoPost(p) ? (
|
||||
<video
|
||||
src={p.mediaUrl}
|
||||
poster={p.thumbnailUrl || undefined}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
controls
|
||||
playsInline
|
||||
preload="metadata"
|
||||
/>
|
||||
) : p.mediaUrl || p.thumbnailUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={p.thumbnailUrl || p.mediaUrl || ''}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center text-xs text-gray-400 px-4 text-center">
|
||||
{p.message?.slice(0, 120) || 'Sin media'}
|
||||
</div>
|
||||
)}
|
||||
<span className="absolute top-2 left-2 text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-black/70 text-white">
|
||||
{p.platform === 'instagram' ? 'IG' : 'FB'}
|
||||
{p.mediaType ? ` · ${p.mediaType}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
<PostMedia post={p} />
|
||||
<div className="p-3 space-y-2 flex-1 flex flex-col">
|
||||
<p className="text-[11px] text-gray-500">
|
||||
{fmtDate(p.createdAt)}
|
||||
{p.mediaProductType
|
||||
? ` · ${p.mediaProductType}`
|
||||
: ''}
|
||||
</p>
|
||||
{p.message ? (
|
||||
<p className="text-xs text-gray-800 line-clamp-3 whitespace-pre-wrap">
|
||||
@@ -575,6 +686,11 @@ export default function SocialDashboard({
|
||||
<Eye className="w-3 h-3" /> {n(p.reach)} alc.
|
||||
</span>
|
||||
)}
|
||||
{p.impressions != null && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Eye className="w-3 h-3" /> {n(p.impressions)} views
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{p.permalink && (
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user