const { useState: useStatePP, useMemo: useMemoPP } = React; window.PlatformPanel = function PlatformPanel({ platform, ads, selected, onToggleAd, onExpandAd, minimal, density, showWinnersBadge, }) { const p = PLATFORMS[platform] || PLATFORMS.foreplay; const [sortKey, setSortKey] = useStatePP('score'); const SORTS = platform === 'adspy' ? ['score', 'createdAt', 'daysRunning', 'likes', 'views'] : platform === 'brandsearch' ? ['score', 'createdAt', 'daysRunning', 'views', 'likes', 'engagementRate', 'reach', 'spend'] : ['score', 'createdAt', 'daysRunning']; const activeSortKey = SORTS.includes(sortKey) ? sortKey : 'score'; const sortedAds = useMemoPP(() => { const copy = [...ads]; if (activeSortKey === 'score') { copy.sort((a, b) => (b.score ?? -1) - (a.score ?? -1)); } else if (activeSortKey === 'createdAt') { copy.sort((a, b) => (b.createdAtMs || 0) - (a.createdAtMs || 0)); } else if (activeSortKey === 'daysRunning') { copy.sort((a, b) => (b.daysRunning || 0) - (a.daysRunning || 0)); } else if (activeSortKey === 'likes') { copy.sort((a, b) => (b.likesCount || 0) - (a.likesCount || 0)); } else if (activeSortKey === 'views') { copy.sort((a, b) => (b.viewsCount || 0) - (a.viewsCount || 0)); } else if (activeSortKey === 'engagementRate') { copy.sort((a, b) => (b.engagementRateCount || 0) - (a.engagementRateCount || 0)); } else if (activeSortKey === 'reach') { copy.sort((a, b) => (b.reachCount || 0) - (a.reachCount || 0)); } else if (activeSortKey === 'spend') { copy.sort((a, b) => (b.spendCount || 0) - (a.spendCount || 0)); } return copy; }, [ads, activeSortKey]); const sel = ads.filter(a => selected.has(a.id)).length; const nextSort = () => setSortKey(SORTS[(SORTS.indexOf(activeSortKey) + 1) % SORTS.length]); const sortLabel = activeSortKey === 'score' ? 'Score ↓' : activeSortKey === 'createdAt' ? 'Created at ↓' : activeSortKey === 'daysRunning' ? 'Days running ↓' : activeSortKey === 'likes' ? 'Likes ↓' : activeSortKey === 'views' ? 'Views ↓' : activeSortKey === 'engagementRate' ? 'ER ↓' : activeSortKey === 'reach' ? 'Reach ↓' : 'Spend ↓'; // Transcript-progress indicator: AdSpy / BrandSearch transcripts come from // server-side Whisper / ElevenLabs and arrive incrementally. Show how many // ads in this panel are still being transcribed so users see overall // progress instead of inferring it from per-card pills. const transcriptStats = useMemoPP(() => { if (platform !== 'adspy' && platform !== 'brandsearch') return null; let pending = 0; let done = 0; let total = 0; for (const a of ads) { if (!a.creativeUrl) continue; total += 1; const status = a.transcriptStatus || null; const has = a.transcript && a.transcript.length > 0; if (has) { done += 1; continue; } if (status === 'fallback' || status === 'unavailable' || status === 'empty') { done += 1; continue; } pending += 1; } return { pending, done, total }; }, [ads, platform]); return (
{p.label} {ads.length} ads {transcriptStats && transcriptStats.pending > 0 && ( Transcribing {transcriptStats.done}/{transcriptStats.total} )} {showWinnersBadge && ( Winners only )}
{sel > 0 && {sel} selected}
{sortedAds.map(a => ( onToggleAd(a.id)} onExpand={onExpandAd} minimal={minimal} /> ))}
); };