'use client' import { useAuth } from '@/lib/auth/context' import { useCallback, useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { motion } from 'framer-motion' import { getSite, type Site } from '@/lib/api/sites' import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, getCountries, getCities, getRegions, getBrowsers, getOS, getDevices, getScreenResolutions, getEntryPages, getExitPages, getDashboard, getCampaigns, getPerformanceByPage, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats' import { formatNumber, formatDuration, getDateRange } from '@ciphera-net/ui' import { toast } from '@ciphera-net/ui' import { getAuthErrorMessage } from '@ciphera-net/ui' import { LoadingOverlay, Button } from '@ciphera-net/ui' import { Select, DatePicker, DownloadIcon } from '@ciphera-net/ui' 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 Campaigns from '@/components/dashboard/Campaigns' 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 const [site, setSite] = useState(null) const [loading, setLoading] = useState(true) const [stats, setStats] = useState({ pageviews: 0, visitors: 0, bounce_rate: 0, avg_duration: 0 }) const [prevStats, setPrevStats] = useState(undefined) const [realtime, setRealtime] = useState(0) const [dailyStats, setDailyStats] = useState([]) const [prevDailyStats, setPrevDailyStats] = useState(undefined) const [topPages, setTopPages] = useState([]) const [entryPages, setEntryPages] = useState([]) const [exitPages, setExitPages] = useState([]) const [topReferrers, setTopReferrers] = useState([]) const [countries, setCountries] = useState([]) const [cities, setCities] = useState([]) const [regions, setRegions] = useState([]) const [browsers, setBrowsers] = useState([]) const [os, setOS] = useState([]) const [devices, setDevices] = useState([]) const [screenResolutions, setScreenResolutions] = useState([]) const [performance, setPerformance] = useState<{ lcp: number, cls: number, inp: number }>({ lcp: 0, cls: 0, inp: 0 }) const [performanceByPage, setPerformanceByPage] = useState(null) const [goalCounts, setGoalCounts] = useState>([]) const [campaigns, setCampaigns] = useState([]) const [dateRange, setDateRange] = useState(getDateRange(30)) const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) const [isExportModalOpen, setIsExportModalOpen] = useState(false) const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>('hour') const [multiDayInterval, setMultiDayInterval] = useState<'hour' | 'day'>('day') const [isSettingsLoaded, setIsSettingsLoaded] = useState(false) const [lastUpdatedAt, setLastUpdatedAt] = useState(null) const [, setTick] = useState(0) // Load settings from localStorage useEffect(() => { try { const savedSettings = localStorage.getItem('pulse_dashboard_settings') if (savedSettings) { const settings = JSON.parse(savedSettings) // Restore date range if (settings.type === 'today') { const today = new Date().toISOString().split('T')[0] setDateRange({ start: today, end: today }) } else if (settings.type === '7') { setDateRange(getDateRange(7)) } else if (settings.type === '30') { setDateRange(getDateRange(30)) } else if (settings.type === 'custom' && settings.dateRange) { setDateRange(settings.dateRange) } // Restore intervals if (settings.todayInterval) setTodayInterval(settings.todayInterval) if (settings.multiDayInterval) setMultiDayInterval(settings.multiDayInterval) } } catch (e) { console.error('Failed to load dashboard settings', e) } finally { setIsSettingsLoaded(true) } }, []) // 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) { console.error('Failed to save dashboard settings', e) } } // Save intervals when they change useEffect(() => { if (!isSettingsLoaded) return // Determine current type 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, isSettingsLoaded]) // dateRange is handled in saveSettings/onChange // * Tick every 1s so "Live ยท Xs ago" counts in real time useEffect(() => { const interval = setInterval(() => setTick((t) => t + 1), 1000) return () => clearInterval(interval) }, []) const getPreviousDateRange = useCallback((start: string, end: string) => { const startDate = new Date(start) const endDate = new Date(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] } }, []) const loadData = useCallback(async (silent = false) => { try { if (!silent) setLoading(true) const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval const [data, prevStatsData, prevDailyStatsData, campaignsData] = await Promise.all([ getDashboard(siteId, dateRange.start, dateRange.end, 10, interval), (async () => { const prevRange = getPreviousDateRange(dateRange.start, dateRange.end) return getStats(siteId, prevRange.start, prevRange.end) })(), (async () => { const prevRange = getPreviousDateRange(dateRange.start, dateRange.end) return getDailyStats(siteId, prevRange.start, prevRange.end, interval) })(), getCampaigns(siteId, dateRange.start, dateRange.end, 100), ]) setSite(data.site) setStats(data.stats || { pageviews: 0, visitors: 0, bounce_rate: 0, avg_duration: 0 }) setRealtime(data.realtime_visitors || 0) setDailyStats(Array.isArray(data.daily_stats) ? data.daily_stats : []) setPrevStats(prevStatsData) setPrevDailyStats(prevDailyStatsData) setTopPages(Array.isArray(data.top_pages) ? data.top_pages : []) setEntryPages(Array.isArray(data.entry_pages) ? data.entry_pages : []) setExitPages(Array.isArray(data.exit_pages) ? data.exit_pages : []) setTopReferrers(Array.isArray(data.top_referrers) ? data.top_referrers : []) setCountries(Array.isArray(data.countries) ? data.countries : []) setCities(Array.isArray(data.cities) ? data.cities : []) setRegions(Array.isArray(data.regions) ? data.regions : []) setBrowsers(Array.isArray(data.browsers) ? data.browsers : []) setOS(Array.isArray(data.os) ? data.os : []) setDevices(Array.isArray(data.devices) ? data.devices : []) setScreenResolutions(Array.isArray(data.screen_resolutions) ? data.screen_resolutions : []) setPerformance(data.performance || { lcp: 0, cls: 0, inp: 0 }) setPerformanceByPage(data.performance_by_page ?? null) setGoalCounts(Array.isArray(data.goal_counts) ? data.goal_counts : []) setCampaigns(Array.isArray(campaignsData) ? campaignsData : []) setLastUpdatedAt(Date.now()) } catch (error: unknown) { if (!silent) { toast.error(getAuthErrorMessage(error) || 'Failed to load data: ' + ((error as Error)?.message || 'Unknown error')) } } finally { if (!silent) setLoading(false) } }, [siteId, dateRange, todayInterval, multiDayInterval]) const loadRealtime = useCallback(async () => { try { const data = await getRealtime(siteId) setRealtime(data.visitors) } catch (error) { // Silently fail for realtime updates } }, [siteId]) useEffect(() => { if (isSettingsLoaded) loadData() const interval = setInterval(() => { loadData(true) loadRealtime() }, 30000) return () => clearInterval(interval) }, [siteId, dateRange, todayInterval, multiDayInterval, isSettingsLoaded, loadData, loadRealtime]) if (loading) { return } if (!site) { return (

Site not found

) } return (

{site.name}

{site.domain}

{/* Realtime Indicator */}