refactor: optimize data loading and auto-refresh logic in dashboard components, and enhance performance with useCallback for better efficiency
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useParams, useSearchParams, useRouter } from 'next/navigation'
|
import { useParams, useSearchParams, useRouter } from 'next/navigation'
|
||||||
import { getPublicDashboard, getPublicStats, getPublicDailyStats, getPublicRealtime, getPublicPerformanceByPage, type DashboardData, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
|
import { getPublicDashboard, getPublicStats, getPublicDailyStats, getPublicRealtime, getPublicPerformanceByPage, type DashboardData, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
|
||||||
import { toast } from '@ciphera-net/ui'
|
import { toast } from '@ciphera-net/ui'
|
||||||
@@ -80,29 +80,13 @@ export default function PublicDashboardPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// * Auto-refresh interval: chart, KPIs, and realtime count update every 30 seconds
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
if (data && !isPasswordProtected) {
|
|
||||||
loadDashboard(true)
|
|
||||||
loadRealtime()
|
|
||||||
}
|
|
||||||
}, 30000)
|
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
|
||||||
}, [data, isPasswordProtected, dateRange, todayInterval, multiDayInterval, password])
|
|
||||||
|
|
||||||
// * Tick every 1s so "Live · Xs ago" counts in real time
|
// * Tick every 1s so "Live · Xs ago" counts in real time
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => setTick((t) => t + 1), 1000)
|
const interval = setInterval(() => setTick((t) => t + 1), 1000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
const loadRealtime = useCallback(async () => {
|
||||||
loadDashboard()
|
|
||||||
}, [siteId, dateRange, todayInterval, multiDayInterval])
|
|
||||||
|
|
||||||
const loadRealtime = async () => {
|
|
||||||
try {
|
try {
|
||||||
const auth = {
|
const auth = {
|
||||||
password,
|
password,
|
||||||
@@ -122,9 +106,9 @@ export default function PublicDashboardPage() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail for realtime updates
|
// Silently fail for realtime updates
|
||||||
}
|
}
|
||||||
}
|
}, [siteId, password, captchaId, captchaSolution, captchaToken, data])
|
||||||
|
|
||||||
const loadDashboard = async (silent = false) => {
|
const loadDashboard = useCallback(async (silent = false) => {
|
||||||
try {
|
try {
|
||||||
if (!silent) setLoading(true)
|
if (!silent) setLoading(true)
|
||||||
|
|
||||||
@@ -186,7 +170,22 @@ export default function PublicDashboardPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
if (!silent) setLoading(false)
|
if (!silent) setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}, [siteId, dateRange, todayInterval, multiDayInterval, password, captchaId, captchaSolution, captchaToken])
|
||||||
|
|
||||||
|
// * Auto-refresh interval: chart, KPIs, and realtime count update every 30 seconds
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && !isPasswordProtected) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
loadDashboard(true)
|
||||||
|
loadRealtime()
|
||||||
|
}, 30000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [data, isPasswordProtected, dateRange, todayInterval, multiDayInterval, password, loadDashboard, loadRealtime])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDashboard()
|
||||||
|
}, [siteId, dateRange, todayInterval, multiDayInterval, loadDashboard])
|
||||||
|
|
||||||
const handlePasswordSubmit = (e: React.FormEvent) => {
|
const handlePasswordSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useAuth } from '@/lib/auth/context'
|
import { useAuth } from '@/lib/auth/context'
|
||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { getSite, type Site } from '@/lib/api/sites'
|
import { getSite, type Site } from '@/lib/api/sites'
|
||||||
@@ -128,15 +128,13 @@ export default function SiteDashboardPage() {
|
|||||||
}, [todayInterval, multiDayInterval, isSettingsLoaded]) // dateRange is handled in saveSettings/onChange
|
}, [todayInterval, multiDayInterval, isSettingsLoaded]) // dateRange is handled in saveSettings/onChange
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSettingsLoaded) {
|
if (isSettingsLoaded) loadData()
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
loadData(true)
|
loadData(true)
|
||||||
loadRealtime()
|
loadRealtime()
|
||||||
}, 30000) // * Chart, KPIs, and realtime count update every 30 seconds
|
}, 30000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [siteId, dateRange, todayInterval, multiDayInterval, isSettingsLoaded])
|
}, [siteId, dateRange, todayInterval, multiDayInterval, isSettingsLoaded, loadData, loadRealtime])
|
||||||
|
|
||||||
// * Tick every 1s so "Live · Xs ago" counts in real time
|
// * Tick every 1s so "Live · Xs ago" counts in real time
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -144,31 +142,20 @@ export default function SiteDashboardPage() {
|
|||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const getPreviousDateRange = (start: string, end: string) => {
|
const getPreviousDateRange = useCallback((start: string, end: string) => {
|
||||||
const startDate = new Date(start)
|
const startDate = new Date(start)
|
||||||
const endDate = new Date(end)
|
const endDate = new Date(end)
|
||||||
const duration = endDate.getTime() - startDate.getTime()
|
const duration = endDate.getTime() - startDate.getTime()
|
||||||
|
|
||||||
// * If duration is 0 (Today), set previous range to yesterday
|
|
||||||
if (duration === 0) {
|
if (duration === 0) {
|
||||||
const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000)
|
const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000)
|
||||||
const prevStart = prevEnd
|
return { start: prevEnd.toISOString().split('T')[0], end: prevEnd.toISOString().split('T')[0] }
|
||||||
return {
|
|
||||||
start: prevStart.toISOString().split('T')[0],
|
|
||||||
end: prevEnd.toISOString().split('T')[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000)
|
const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000)
|
||||||
const prevStart = new Date(prevEnd.getTime() - duration)
|
const prevStart = new Date(prevEnd.getTime() - duration)
|
||||||
|
return { start: prevStart.toISOString().split('T')[0], end: prevEnd.toISOString().split('T')[0] }
|
||||||
|
}, [])
|
||||||
|
|
||||||
return {
|
const loadData = useCallback(async (silent = false) => {
|
||||||
start: prevStart.toISOString().split('T')[0],
|
|
||||||
end: prevEnd.toISOString().split('T')[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadData = async (silent = false) => {
|
|
||||||
try {
|
try {
|
||||||
if (!silent) setLoading(true)
|
if (!silent) setLoading(true)
|
||||||
const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval
|
const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval
|
||||||
@@ -217,16 +204,16 @@ export default function SiteDashboardPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
if (!silent) setLoading(false)
|
if (!silent) setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}, [siteId, dateRange, todayInterval, multiDayInterval])
|
||||||
|
|
||||||
const loadRealtime = async () => {
|
const loadRealtime = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getRealtime(siteId)
|
const data = await getRealtime(siteId)
|
||||||
setRealtime(data.visitors)
|
setRealtime(data.visitors)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail for realtime updates
|
// Silently fail for realtime updates
|
||||||
}
|
}
|
||||||
}
|
}, [siteId])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
|
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
|
||||||
|
|||||||
@@ -650,8 +650,8 @@ export default function Chart({
|
|||||||
strokeDasharray="4 4"
|
strokeDasharray="4 4"
|
||||||
strokeOpacity={0.7}
|
strokeOpacity={0.7}
|
||||||
label={{
|
label={{
|
||||||
value: `Avg: ${metric === 'bounce_rate' ? `${Math.round(avg)}%` : metric === 'avg_duration' ? formatDuration(avg) : formatAxisValue(avg)}`,
|
value: `Avg: ${metric === 'bounce_rate' ? `${Math.round(avg)}%` : metric === 'avg_duration' ? formatAxisDuration(avg) : formatAxisValue(avg)}`,
|
||||||
position: 'right',
|
position: 'insideTopRight',
|
||||||
fill: colors.axis,
|
fill: colors.axis,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user