'use client';
/**
* Analytics Detail Page — Tabbed drill-down view for analytics data.
*
* Displays detailed tables and summary panels for:
* - Top Songs (MEJORES CANCIONES)
* - Income Groups (GRUPOS DE INGRESOS MÁS ALTOS)
* - Territories (TERRITORIOS PRINCIPALES)
* - Exploitation Sources (RESUMEN DE FUENTES DE EXPLOTACIÓN)
*
* Accessed via "Ver más" links on the analytics cards.
*/
import { useState, useMemo, Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import { clientPortal } from '@/lib/mock-data';
import { useCompactCurrencyFormatter } from '@/lib/hooks/useCurrencyFormatter';
import { chartColorArray } from '@/lib/constants/theme';
import type {
PortalSongDetail,
PortalIncomeGroupDetail,
PortalTerritoryDetail,
PortalSourceDetail,
} from '@/lib/mock-data/types';
import PieChartWidget from '@/components/charts/PieChartWidget';
import BarChartWidget from '@/components/charts/BarChartWidget';
// ─── Types ───────────────────────────────────────────────────────────────────
type TabId = 'songs' | 'income' | 'territories' | 'sources';
interface TabConfig {
id: TabId;
label: string;
}
const TABS: TabConfig[] = [
{ id: 'songs', label: 'MEJORES CANCIONES' },
{ id: 'income', label: 'GRUPOS DE INGRESOS MÁS ALTOS' },
{ id: 'territories', label: 'TERRITORIOS PRINCIPALES' },
{ id: 'sources', label: 'RESUMEN DE FUENTES DE EXPLOTACIÓN' },
];
// ─── Helper Components ───────────────────────────────────────────────────────
function ChevronIcon({ expanded }: { expanded: boolean }) {
return (
);
}
function PercentageBadge({ value }: { value: number }) {
const isPositive = value >= 0;
return (
{isPositive ? '+' : ''}{value.toFixed(1)}%
);
}
function TotalEarningsCard({ total, trend }: { total: number; trend: number }) {
const fmtMoney = useCompactCurrencyFormatter();
return (
Ganancias totales
{fmtMoney(total)}
);
}
// ─── Tab Content Components ──────────────────────────────────────────────────
function SongsTab({ data }: { data: PortalSongDetail[] }) {
const [expandedRows, setExpandedRows] = useState>(new Set());
const fmtMoney = useCompactCurrencyFormatter();
const sorted = useMemo(
() => [...data].sort((a, b) => b.currentPeriodAmount - a.currentPeriodAmount),
[data]
);
const total = sorted.reduce((sum, s) => sum + s.currentPeriodAmount, 0);
const totalLast = sorted.reduce((sum, s) => sum + s.lastPeriodAmount, 0);
const trend = totalLast > 0 ? ((total - totalLast) / totalLast) * 100 : 0;
const toggleRow = (id: string) => {
setExpandedRows((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};
return (
{/* Left Panel */}
{sorted.slice(0, 10).map((song, i) => {
const pct = total > 0 ? (song.currentPeriodAmount / total) * 100 : 0;
return (
-
{i + 1}. {song.title}
{song.composers.join(', ')}
{fmtMoney(song.currentPeriodAmount)}
);
})}
{/* Right Panel - Table */}
{/* Table Header */}
#
Las 20 Mejores Canciones
Último Período
Período Actual
{/* Table Body */}
{sorted.map((song, i) => {
const isExpanded = expandedRows.has(song.id);
return (
toggleRow(song.id)}
>
{i + 1}
{song.title}
{song.composers.join(', ')}
{fmtMoney(song.lastPeriodAmount)}
{fmtMoney(song.currentPeriodAmount)}
{/* Expanded sub-row */}
{isExpanded && (
Categoría superior
{song.topCategory}
{fmtMoney(song.topCategoryAmount)}
Fuente principal
{song.topSource}
{fmtMoney(song.topSourceAmount)}
Territorio superior
{song.topTerritory}
{fmtMoney(song.topTerritoryAmount)}
)}
);
})}
);
}
function IncomeTab({ data }: { data: PortalIncomeGroupDetail[] }) {
const [expandedRows, setExpandedRows] = useState>(new Set());
const fmtMoney = useCompactCurrencyFormatter();
const sorted = useMemo(
() => [...data].sort((a, b) => b.currentPeriodAmount - a.currentPeriodAmount),
[data]
);
const total = sorted.reduce((sum, g) => sum + g.currentPeriodAmount, 0);
const totalLast = sorted.reduce((sum, g) => sum + g.lastPeriodAmount, 0);
const trend = totalLast > 0 ? ((total - totalLast) / totalLast) * 100 : 0;
const toggleRow = (category: string) => {
setExpandedRows((prev) => {
const next = new Set(prev);
if (next.has(category)) next.delete(category);
else next.add(category);
return next;
});
};
const chartData = sorted.map((g) => ({ name: g.category, value: g.currentPeriodAmount }));
return (
{/* Left Panel */}
{sorted.map((group, i) => (
-
{group.category}
{fmtMoney(group.currentPeriodAmount)}
))}
{/* Right Panel - Table */}
#
Grupos De Ingresos Más Altos
Último Período
Período Actual
{sorted.map((group, i) => {
const isExpanded = expandedRows.has(group.category);
return (
toggleRow(group.category)}
>
{i + 1}
{group.category}
{fmtMoney(group.lastPeriodAmount)}
{fmtMoney(group.currentPeriodAmount)}
{isExpanded && (
{group.subCategories.map((sub) => (
{sub.name}
{fmtMoney(sub.lastPeriodAmount)}
{fmtMoney(sub.currentPeriodAmount)}
))}
)}
);
})}
);
}
function TerritoriesTab({ data }: { data: PortalTerritoryDetail[] }) {
const fmtMoney = useCompactCurrencyFormatter();
const sorted = useMemo(
() => [...data].sort((a, b) => b.currentPeriodAmount - a.currentPeriodAmount),
[data]
);
const total = sorted.reduce((sum, t) => sum + t.currentPeriodAmount, 0);
const totalLast = sorted.reduce((sum, t) => sum + t.lastPeriodAmount, 0);
const trend = totalLast > 0 ? ((total - totalLast) / totalLast) * 100 : 0;
const chartData = sorted.slice(0, 10).map((t) => ({
country: t.country,
amount: t.currentPeriodAmount,
}));
return (
{/* Left Panel */}
{sorted.slice(0, 10).map((territory, i) => (
-
{territory.country}
{fmtMoney(territory.currentPeriodAmount)}
))}
{/* Right Panel - Table */}
#
Los 32 Territorios Principales
Último Período
Período Actual
{sorted.map((territory, i) => (
{i + 1}
{territory.country}
{fmtMoney(territory.lastPeriodAmount)}
{fmtMoney(territory.currentPeriodAmount)}
))}
);
}
function SourcesTab({ data }: { data: PortalSourceDetail[] }) {
const [expandedRows, setExpandedRows] = useState>(new Set());
const fmtMoney = useCompactCurrencyFormatter();
const sorted = useMemo(
() => [...data].sort((a, b) => b.currentPeriodAmount - a.currentPeriodAmount),
[data]
);
const total = sorted.reduce((sum, s) => sum + s.currentPeriodAmount, 0);
const totalLast = sorted.reduce((sum, s) => sum + s.lastPeriodAmount, 0);
const trend = totalLast > 0 ? ((total - totalLast) / totalLast) * 100 : 0;
const toggleRow = (platform: string) => {
setExpandedRows((prev) => {
const next = new Set(prev);
if (next.has(platform)) next.delete(platform);
else next.add(platform);
return next;
});
};
return (
{/* Left Panel */}
{sorted.slice(0, 10).map((source, i) => {
const pct = total > 0 ? (source.currentPeriodAmount / total) * 100 : 0;
return (
-
{source.platform}
{fmtMoney(source.currentPeriodAmount)}
);
})}
{/* Right Panel - Table */}
#
Las 20 Fuentes
Último Período
Período Actual
{sorted.map((source, i) => {
const isExpanded = expandedRows.has(source.platform);
return (
toggleRow(source.platform)}
>
{i + 1}
{source.platform}
{fmtMoney(source.lastPeriodAmount)}
{fmtMoney(source.currentPeriodAmount)}
{isExpanded && (
Top Canción
{source.topSong}
{fmtMoney(source.topSongAmount)}
Categoría superior
{source.topCategory}
{fmtMoney(source.topCategoryAmount)}
Territorio superior
{source.topTerritory}
{fmtMoney(source.topTerritoryAmount)}
)}
);
})}
);
}
// ─── Inner Page Component (uses useSearchParams) ─────────────────────────────
function AnalyticsDetailContent() {
const searchParams = useSearchParams();
const tabParam = searchParams.get('tab') as TabId | null;
const initialTab: TabId = tabParam && TABS.some((t) => t.id === tabParam) ? tabParam : 'songs';
const [activeTab, setActiveTab] = useState(initialTab);
const selectedPeriod = clientPortal.periods[0];
return (
{/* Period Selector */}
Período de regalías:
{selectedPeriod?.label ?? 'N/A'}
{/* Tab Navigation */}
{/* Tab Content */}
{activeTab === 'songs' &&
}
{activeTab === 'income' &&
}
{activeTab === 'territories' &&
}
{activeTab === 'sources' &&
}
);
}
// ─── Page Export ──────────────────────────────────────────────────────────────
export default function AnalyticsDetailPage() {
return (
Cargando...}>
);
}