'use client' import { useState, useEffect } from 'react' import { motion } from 'framer-motion' import { logger } from '@/lib/utils/logger' import { formatNumber } from '@ciphera-net/ui' import { useTabListKeyboard } from '@/lib/hooks/useTabListKeyboard' import { TopPage, getTopPages, getEntryPages, getExitPages } from '@/lib/api/stats' import Link from 'next/link' import { Files, FrameCornersIcon } from '@phosphor-icons/react' import { Modal, ArrowUpRightIcon, ArrowRightIcon, LayoutDashboardIcon } from '@ciphera-net/ui' import { ListSkeleton } from '@/components/skeletons' import VirtualList from './VirtualList' import { type DimensionFilter } from '@/lib/filters' interface ContentStatsProps { topPages: TopPage[] entryPages: TopPage[] exitPages: TopPage[] domain: string collectPagePaths?: boolean siteId: string dateRange: { start: string, end: string } onFilter?: (filter: DimensionFilter) => void } type Tab = 'top_pages' | 'entry_pages' | 'exit_pages' const LIMIT = 7 export default function ContentStats({ topPages, entryPages, exitPages, domain, collectPagePaths = true, siteId, dateRange, onFilter }: ContentStatsProps) { const [activeTab, setActiveTab] = useState('top_pages') const handleTabKeyDown = useTabListKeyboard() const [isModalOpen, setIsModalOpen] = useState(false) const [modalSearch, setModalSearch] = useState('') const [fullData, setFullData] = useState([]) const [isLoadingFull, setIsLoadingFull] = useState(false) // Filter out generic "/" entries when page paths are disabled (all traffic shows as "/") const filterGenericPaths = (pages: TopPage[]) => { if (!collectPagePaths) return [] // Filter out pages that are just "/" with high traffic (indicator of disabled tracking) return pages.filter(p => p.path && p.path !== '') } useEffect(() => { if (isModalOpen) { const fetchData = async () => { setIsLoadingFull(true) try { let data: TopPage[] = [] if (activeTab === 'top_pages') { data = await getTopPages(siteId, dateRange.start, dateRange.end, 100) } else if (activeTab === 'entry_pages') { data = await getEntryPages(siteId, dateRange.start, dateRange.end, 100) } else if (activeTab === 'exit_pages') { data = await getExitPages(siteId, dateRange.start, dateRange.end, 100) } setFullData(filterGenericPaths(data)) } catch (e) { logger.error(e) } finally { setIsLoadingFull(false) } } fetchData() } else { setFullData([]) } }, [isModalOpen, activeTab, siteId, dateRange, collectPagePaths]) const getData = () => { switch (activeTab) { case 'top_pages': return filterGenericPaths(topPages) case 'entry_pages': return filterGenericPaths(entryPages) case 'exit_pages': return filterGenericPaths(exitPages) default: return [] } } const data = getData() const totalPageviews = data.reduce((sum, p) => sum + p.pageviews, 0) const hasData = data && data.length > 0 const displayedData = hasData ? data.slice(0, LIMIT) : [] const emptySlots = Math.max(0, LIMIT - displayedData.length) const showViewAll = hasData && data.length > LIMIT const getTabLabel = (tab: Tab) => { switch (tab) { case 'top_pages': return 'Top Pages' case 'entry_pages': return 'Entry' case 'exit_pages': return 'Exit' } } return ( <>

Pages

{showViewAll && ( )}
{(['top_pages', 'entry_pages', 'exit_pages'] as Tab[]).map((tab) => ( ))}
{!collectPagePaths ? (

Page path tracking is disabled in site settings

) : hasData ? ( <> {displayedData.map((page, idx) => { const maxPv = displayedData[0]?.pageviews ?? 0 const barWidth = maxPv > 0 ? (page.pageviews / maxPv) * 75 : 0 return (
onFilter?.({ dimension: 'page', operator: 'is', values: [page.path] })} className={`relative flex items-center justify-between h-9 group hover:bg-neutral-50/50 dark:hover:bg-neutral-800/50 rounded-lg px-2 -mx-2 transition-colors${onFilter ? ' cursor-pointer' : ''}`} >
{totalPageviews > 0 ? `${Math.round((page.pageviews / totalPageviews) * 100)}%` : ''} {formatNumber(page.pageviews)}
) })} {Array.from({ length: emptySlots }).map((_, i) => (
{ setIsModalOpen(false); setModalSearch('') }} title={getTabLabel(activeTab)} className="max-w-2xl" >
setModalSearch(e.target.value)} placeholder="Search pages..." className="w-full px-3 py-2 mb-3 text-sm bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg text-neutral-900 dark:text-white placeholder-neutral-400 dark:placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-brand-orange/50" />
{isLoadingFull ? (
) : (() => { const modalData = (fullData.length > 0 ? fullData : data).filter(p => !modalSearch || p.path.toLowerCase().includes(modalSearch.toLowerCase())) const modalTotal = modalData.reduce((sum, p) => sum + p.pageviews, 0) return ( { const canFilter = onFilter && page.path return (
{ if (canFilter) { onFilter({ dimension: 'page', operator: 'is', values: [page.path] }); setIsModalOpen(false) } }} className={`flex items-center justify-between h-9 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 transition-colors${canFilter ? ' cursor-pointer' : ''}`} >
{page.path}
{modalTotal > 0 ? `${Math.round((page.pageviews / modalTotal) * 100)}%` : ''} {formatNumber(page.pageviews)}
) }} /> ) })()}
) }