'use client' import { useCallback, useEffect, useRef, useState } from 'react' import { useParams } from 'next/navigation' import { getDateRange, formatDate } from '@ciphera-net/ui' import { Select, DatePicker } from '@ciphera-net/ui' import { toast } from '@ciphera-net/ui' import dynamic from 'next/dynamic' import { getFrustrationSummary, getRageClicks, getDeadClicks, getFrustrationByPage, type FrustrationSummary, type FrustrationElement, type FrustrationByPage, } from '@/lib/api/stats' import FrustrationSummaryCards from '@/components/behavior/FrustrationSummaryCards' import FrustrationTable from '@/components/behavior/FrustrationTable' import FrustrationByPageTable from '@/components/behavior/FrustrationByPageTable' import FrustrationTrend from '@/components/behavior/FrustrationTrend' import { useDashboard } from '@/lib/swr/dashboard' const ScrollDepth = dynamic(() => import('@/components/dashboard/ScrollDepth')) const TABLE_LIMIT = 7 function getThisWeekRange(): { start: string; end: string } { const today = new Date() const dayOfWeek = today.getDay() const monday = new Date(today) monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) return { start: formatDate(monday), end: formatDate(today) } } function getThisMonthRange(): { start: string; end: string } { const today = new Date() const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) return { start: formatDate(firstOfMonth), end: formatDate(today) } } export default function BehaviorPage() { const params = useParams() const siteId = params.id as string const [period, setPeriod] = useState('30') const [dateRange, setDateRange] = useState(() => getDateRange(30)) const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) // Frustration data const [summary, setSummary] = useState(null) const [rageClicks, setRageClicks] = useState<{ items: FrustrationElement[]; total: number }>({ items: [], total: 0 }) const [deadClicks, setDeadClicks] = useState<{ items: FrustrationElement[]; total: number }>({ items: [], total: 0 }) const [byPage, setByPage] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(false) const refreshRef = useRef | null>(null) // Fetch dashboard data for scroll depth (goal_counts + stats) const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) const fetchData = useCallback(async () => { try { const [summaryData, rageData, deadData, pageData] = await Promise.all([ getFrustrationSummary(siteId, dateRange.start, dateRange.end), getRageClicks(siteId, dateRange.start, dateRange.end, TABLE_LIMIT), getDeadClicks(siteId, dateRange.start, dateRange.end, TABLE_LIMIT), getFrustrationByPage(siteId, dateRange.start, dateRange.end), ]) setSummary(summaryData) setRageClicks(rageData) setDeadClicks(deadData) setByPage(pageData) setError(false) } catch { setError(true) toast.error('Failed to load behavior data') } finally { setLoading(false) } }, [siteId, dateRange.start, dateRange.end]) // Fetch on mount and when date range changes useEffect(() => { setLoading(true) fetchData() }, [fetchData]) // 60-second refresh interval useEffect(() => { refreshRef.current = setInterval(fetchData, 60_000) return () => { if (refreshRef.current) clearInterval(refreshRef.current) } }, [fetchData]) useEffect(() => { const domain = dashboard?.site?.domain document.title = domain ? `Behavior · ${domain} | Pulse` : 'Behavior | Pulse' }, [dashboard?.site?.domain]) const fetchAllRage = useCallback( () => getRageClicks(siteId, dateRange.start, dateRange.end, 100), [siteId, dateRange.start, dateRange.end] ) const fetchAllDead = useCallback( () => getDeadClicks(siteId, dateRange.start, dateRange.end, 100), [siteId, dateRange.start, dateRange.end] ) return (
{/* Header */}

Behavior

Frustration signals and user engagement patterns