'use client' import { useAuth } from '@/lib/auth/context' import { logger } from '@/lib/utils/logger' import { useEffect, useState, useMemo } from 'react' import { useParams, useRouter } from 'next/navigation' import { motion } from 'framer-motion' import { getPerformanceByPage, type Stats, type DailyStat } from '@/lib/api/stats' import { getDateRange } from '@ciphera-net/ui' import { toast } from '@ciphera-net/ui' import { Button } from '@ciphera-net/ui' import { Select, DatePicker, DownloadIcon } from '@ciphera-net/ui' import { DashboardSkeleton, useMinimumLoading } from '@/components/skeletons' import ExportModal from '@/components/dashboard/ExportModal' import ContentStats from '@/components/dashboard/ContentStats' import TopReferrers from '@/components/dashboard/TopReferrers' import Locations from '@/components/dashboard/Locations' import TechSpecs from '@/components/dashboard/TechSpecs' import Chart from '@/components/dashboard/Chart' import PerformanceStats from '@/components/dashboard/PerformanceStats' import GoalStats from '@/components/dashboard/GoalStats' import ScrollDepth from '@/components/dashboard/ScrollDepth' import Campaigns from '@/components/dashboard/Campaigns' import { useDashboardOverview, useDashboardPages, useDashboardLocations, useDashboardDevices, useDashboardReferrers, useDashboardPerformance, useDashboardGoals, useRealtime, useStats, useDailyStats, useCampaigns, } from '@/lib/swr/dashboard' function loadSavedSettings(): { type?: string dateRange?: { start: string; end: string } todayInterval?: 'minute' | 'hour' multiDayInterval?: 'hour' | 'day' } | null { if (typeof window === 'undefined') return null try { const saved = localStorage.getItem('pulse_dashboard_settings') return saved ? JSON.parse(saved) : null } catch { return null } } function getInitialDateRange(): { start: string; end: string } { const settings = loadSavedSettings() if (settings?.type === 'today') { const today = new Date().toISOString().split('T')[0] return { start: today, end: today } } if (settings?.type === '7') return getDateRange(7) if (settings?.type === 'custom' && settings.dateRange) return settings.dateRange return getDateRange(30) } export default function SiteDashboardPage() { const { user } = useAuth() const canEdit = user?.role === 'owner' || user?.role === 'admin' const params = useParams() const router = useRouter() const siteId = params.id as string // UI state - initialized from localStorage synchronously to avoid double-fetch const [dateRange, setDateRange] = useState(getInitialDateRange) const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>( () => loadSavedSettings()?.todayInterval || 'hour' ) const [multiDayInterval, setMultiDayInterval] = useState<'hour' | 'day'>( () => loadSavedSettings()?.multiDayInterval || 'day' ) const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) const [isExportModalOpen, setIsExportModalOpen] = useState(false) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) const [, setTick] = useState(0) const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval // Previous period date range for comparison const prevRange = useMemo(() => { const startDate = new Date(dateRange.start) const endDate = new Date(dateRange.end) const duration = endDate.getTime() - startDate.getTime() if (duration === 0) { const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000) return { start: prevEnd.toISOString().split('T')[0], end: prevEnd.toISOString().split('T')[0] } } const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000) const prevStart = new Date(prevEnd.getTime() - duration) return { start: prevStart.toISOString().split('T')[0], end: prevEnd.toISOString().split('T')[0] } }, [dateRange]) // SWR hooks - replace manual useState + useEffect + setInterval polling // Each hook handles its own refresh interval, deduplication, and error retry const { data: overview, isLoading: overviewLoading, error: overviewError } = useDashboardOverview(siteId, dateRange.start, dateRange.end, interval) const { data: pages } = useDashboardPages(siteId, dateRange.start, dateRange.end) const { data: locations } = useDashboardLocations(siteId, dateRange.start, dateRange.end) const { data: devicesData } = useDashboardDevices(siteId, dateRange.start, dateRange.end) const { data: referrers } = useDashboardReferrers(siteId, dateRange.start, dateRange.end) const { data: performanceData } = useDashboardPerformance(siteId, dateRange.start, dateRange.end) const { data: goalsData } = useDashboardGoals(siteId, dateRange.start, dateRange.end) const { data: realtimeData } = useRealtime(siteId) const { data: prevStats } = useStats(siteId, prevRange.start, prevRange.end) const { data: prevDailyStats } = useDailyStats(siteId, prevRange.start, prevRange.end, interval) const { data: campaigns } = useCampaigns(siteId, dateRange.start, dateRange.end) // Derive typed values from SWR data const site = overview?.site ?? null const stats: Stats = overview?.stats ?? { pageviews: 0, visitors: 0, bounce_rate: 0, avg_duration: 0 } const realtime = realtimeData?.visitors ?? overview?.realtime_visitors ?? 0 const dailyStats: DailyStat[] = overview?.daily_stats ?? [] // Show error toast on fetch failure useEffect(() => { if (overviewError) { toast.error('Failed to load dashboard analytics') } }, [overviewError]) // Track when data was last updated (for "Live · Xs ago" display) useEffect(() => { if (overview) setLastUpdatedAt(Date.now()) }, [overview]) // Tick every 1s so "Live · Xs ago" counts in real time useEffect(() => { const timer = setInterval(() => setTick((t) => t + 1), 1000) return () => clearInterval(timer) }, []) // Save settings to localStorage const saveSettings = (type: string, newDateRange?: { start: string; end: string }) => { try { const settings = { type, dateRange: newDateRange || dateRange, todayInterval, multiDayInterval, lastUpdated: Date.now() } localStorage.setItem('pulse_dashboard_settings', JSON.stringify(settings)) } catch (e) { logger.error('Failed to save dashboard settings', e) } } // Save intervals when they change useEffect(() => { let type = 'custom' const today = new Date().toISOString().split('T')[0] if (dateRange.start === today && dateRange.end === today) type = 'today' else if (dateRange.start === getDateRange(7).start) type = '7' else if (dateRange.start === getDateRange(30).start) type = '30' const settings = { type, dateRange, todayInterval, multiDayInterval, lastUpdated: Date.now() } localStorage.setItem('pulse_dashboard_settings', JSON.stringify(settings)) }, [todayInterval, multiDayInterval]) // eslint-disable-line react-hooks/exhaustive-deps -- dateRange saved via saveSettings useEffect(() => { if (site?.domain) document.title = `${site.domain} | Pulse` }, [site?.domain]) const showSkeleton = useMinimumLoading(overviewLoading) if (showSkeleton) { return } if (!site) { return (

Site not found

) } return (

{site.name}

{site.domain}

{/* Realtime Indicator */}