feat: add previous period statistics and enhance data loading in PublicDashboardPage

This commit is contained in:
Usman Baig
2026-01-27 21:23:11 +01:00
parent 5dd785d2be
commit d9c3294eeb
2 changed files with 156 additions and 17 deletions

View File

@@ -2,7 +2,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useParams, useSearchParams, useRouter } from 'next/navigation' import { useParams, useSearchParams, useRouter } from 'next/navigation'
import { getPublicDashboard, type DashboardData } 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 'sonner' import { toast } from 'sonner'
import { LoadingOverlay } from '@ciphera-net/ui' import { LoadingOverlay } from '@ciphera-net/ui'
import Chart from '@/components/dashboard/Chart' import Chart from '@/components/dashboard/Chart'
@@ -48,14 +48,42 @@ export default function PublicDashboardPage() {
const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>('hour') const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>('hour')
const [multiDayInterval, setMultiDayInterval] = useState<'hour' | 'day'>('day') const [multiDayInterval, setMultiDayInterval] = useState<'hour' | 'day'>('day')
// Previous period data
const [prevStats, setPrevStats] = useState<Stats | undefined>(undefined)
const [prevDailyStats, setPrevDailyStats] = useState<DailyStat[] | undefined>(undefined)
const getPreviousDateRange = (start: string, end: string) => {
const startDate = new Date(start)
const endDate = new Date(end)
const duration = endDate.getTime() - startDate.getTime()
// * If duration is 0 (Today), set previous range to yesterday
if (duration === 0) {
const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000)
const prevStart = prevEnd
return {
start: prevStart.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]
}
}
// Auto-refresh interval (for realtime) // Auto-refresh interval (for realtime)
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
// Only refresh realtime count if we have data // Only refresh realtime count if we have data
if (data && !isPasswordProtected) { if (data && !isPasswordProtected) {
loadDashboard(true) loadRealtime()
} }
}, 10000) // 10 seconds }, 30000) // 30 seconds
return () => clearInterval(interval) return () => clearInterval(interval)
}, [data, isPasswordProtected, dateRange, password]) }, [data, isPasswordProtected, dateRange, password])
@@ -64,25 +92,66 @@ export default function PublicDashboardPage() {
loadDashboard() loadDashboard()
}, [siteId, dateRange, todayInterval, multiDayInterval]) }, [siteId, dateRange, todayInterval, multiDayInterval])
const loadRealtime = async () => {
try {
const auth = {
password,
captcha: {
captcha_id: captchaId,
captcha_solution: captchaSolution,
captcha_token: captchaToken
}
}
const realtimeData = await getPublicRealtime(siteId, auth)
if (data) {
setData({
...data,
realtime_visitors: realtimeData.visitors
})
}
} catch (error) {
// Silently fail for realtime updates
}
}
const loadDashboard = async (silent = false) => { const loadDashboard = async (silent = false) => {
try { try {
if (!silent) setLoading(true) if (!silent) setLoading(true)
const dashboardData = await getPublicDashboard( const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval
siteId, const auth = {
dateRange.start,
dateRange.end,
10,
dateRange.start === dateRange.end ? todayInterval : multiDayInterval,
password, password,
{ captcha: {
captcha_id: captchaId, captcha_id: captchaId,
captcha_solution: captchaSolution, captcha_solution: captchaSolution,
captcha_token: captchaToken captcha_token: captchaToken
} }
) }
const [dashboardData, prevStatsData, prevDailyStatsData] = await Promise.all([
getPublicDashboard(
siteId,
dateRange.start,
dateRange.end,
10,
interval,
password,
auth.captcha
),
(async () => {
const prevRange = getPreviousDateRange(dateRange.start, dateRange.end)
return getPublicStats(siteId, prevRange.start, prevRange.end, auth)
})(),
(async () => {
const prevRange = getPreviousDateRange(dateRange.start, dateRange.end)
return getPublicDailyStats(siteId, prevRange.start, prevRange.end, interval, auth)
})()
])
setData(dashboardData) setData(dashboardData)
setPrevStats(prevStatsData)
setPrevDailyStats(prevDailyStatsData)
setIsPasswordProtected(false) setIsPasswordProtected(false)
// Reset captcha // Reset captcha
setCaptchaId('') setCaptchaId('')
@@ -304,8 +373,9 @@ export default function PublicDashboardPage() {
<div className="mb-8"> <div className="mb-8">
<Chart <Chart
data={safeDailyStats} data={safeDailyStats}
prevData={[]} prevData={prevDailyStats}
stats={safeStats} stats={safeStats}
prevStats={prevStats}
interval={dateRange.start === dateRange.end ? todayInterval : multiDayInterval} interval={dateRange.start === dateRange.end ? todayInterval : multiDayInterval}
/> />
</div> </div>
@@ -313,7 +383,23 @@ export default function PublicDashboardPage() {
{/* Performance Stats - Only show if enabled */} {/* Performance Stats - Only show if enabled */}
{performance && data.site?.enable_performance_insights && ( {performance && data.site?.enable_performance_insights && (
<div className="mb-8"> <div className="mb-8">
<PerformanceStats stats={performance} performanceByPage={performance_by_page} /> <PerformanceStats
stats={performance}
performanceByPage={performance_by_page}
siteId={siteId}
startDate={dateRange.start}
endDate={dateRange.end}
getPerformanceByPage={(siteId, startDate, endDate, opts) => {
return getPublicPerformanceByPage(siteId, startDate, endDate, opts, {
password,
captcha: {
captcha_id: captchaId,
captcha_solution: captchaSolution,
captcha_token: captchaToken
}
})
}}
/>
</div> </div>
)} )}

View File

@@ -82,6 +82,18 @@ export interface RealtimeStats {
visitors: number visitors: number
} }
export interface AuthParams {
password?: string
captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
}
function appendAuthParams(params: URLSearchParams, auth?: AuthParams) {
if (auth?.password) params.append('password', auth.password)
if (auth?.captcha?.captcha_id) params.append('captcha_id', auth.captcha.captcha_id)
if (auth?.captcha?.captcha_solution) params.append('captcha_solution', auth.captcha.captcha_solution)
if (auth?.captcha?.captcha_token) params.append('captcha_token', auth.captcha.captcha_token)
}
export async function getStats(siteId: string, startDate?: string, endDate?: string): Promise<Stats> { export async function getStats(siteId: string, startDate?: string, endDate?: string): Promise<Stats> {
const params = new URLSearchParams() const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate) if (startDate) params.append('start_date', startDate)
@@ -90,10 +102,25 @@ export async function getStats(siteId: string, startDate?: string, endDate?: str
return apiRequest<Stats>(`/sites/${siteId}/stats${query ? `?${query}` : ''}`) return apiRequest<Stats>(`/sites/${siteId}/stats${query ? `?${query}` : ''}`)
} }
export async function getPublicStats(siteId: string, startDate?: string, endDate?: string, auth?: AuthParams): Promise<Stats> {
const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate)
if (endDate) params.append('end_date', endDate)
appendAuthParams(params, auth)
const query = params.toString()
return apiRequest<Stats>(`/public/sites/${siteId}/stats${query ? `?${query}` : ''}`)
}
export async function getRealtime(siteId: string): Promise<RealtimeStats> { export async function getRealtime(siteId: string): Promise<RealtimeStats> {
return apiRequest<RealtimeStats>(`/sites/${siteId}/realtime`) return apiRequest<RealtimeStats>(`/sites/${siteId}/realtime`)
} }
export async function getPublicRealtime(siteId: string, auth?: AuthParams): Promise<RealtimeStats> {
const params = new URLSearchParams()
appendAuthParams(params, auth)
return apiRequest<RealtimeStats>(`/public/sites/${siteId}/realtime?${params.toString()}`)
}
export async function getTopPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise<TopPage[]> { export async function getTopPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise<TopPage[]> {
const params = new URLSearchParams() const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate) if (startDate) params.append('start_date', startDate)
@@ -166,6 +193,15 @@ export async function getDailyStats(siteId: string, startDate?: string, endDate?
return apiRequest<{ stats: DailyStat[] }>(`/sites/${siteId}/daily?${params.toString()}`).then(r => r?.stats || []) return apiRequest<{ stats: DailyStat[] }>(`/sites/${siteId}/daily?${params.toString()}`).then(r => r?.stats || [])
} }
export async function getPublicDailyStats(siteId: string, startDate?: string, endDate?: string, interval?: string, auth?: AuthParams): Promise<DailyStat[]> {
const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate)
if (endDate) params.append('end_date', endDate)
if (interval) params.append('interval', interval)
appendAuthParams(params, auth)
return apiRequest<{ stats: DailyStat[] }>(`/public/sites/${siteId}/daily?${params.toString()}`).then(r => r?.stats || [])
}
export async function getEntryPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise<TopPage[]> { export async function getEntryPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise<TopPage[]> {
const params = new URLSearchParams() const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate) if (startDate) params.append('start_date', startDate)
@@ -207,6 +243,25 @@ export async function getPerformanceByPage(
return res?.performance_by_page ?? [] return res?.performance_by_page ?? []
} }
export async function getPublicPerformanceByPage(
siteId: string,
startDate?: string,
endDate?: string,
opts?: { limit?: number; sort?: 'lcp' | 'cls' | 'inp' },
auth?: AuthParams
): Promise<PerformanceByPageStat[]> {
const params = new URLSearchParams()
if (startDate) params.append('start_date', startDate)
if (endDate) params.append('end_date', endDate)
if (opts?.limit != null) params.append('limit', String(opts.limit))
if (opts?.sort) params.append('sort', opts.sort)
appendAuthParams(params, auth)
const res = await apiRequest<{ performance_by_page: PerformanceByPageStat[] }>(
`/public/sites/${siteId}/performance-by-page?${params.toString()}`
)
return res?.performance_by_page ?? []
}
export interface DashboardData { export interface DashboardData {
site: Site site: Site
stats: Stats stats: Stats
@@ -249,10 +304,8 @@ export async function getPublicDashboard(
if (startDate) params.append('start_date', startDate) if (startDate) params.append('start_date', startDate)
if (endDate) params.append('end_date', endDate) if (endDate) params.append('end_date', endDate)
if (interval) params.append('interval', interval) if (interval) params.append('interval', interval)
if (password) params.append('password', password)
if (captcha?.captcha_id) params.append('captcha_id', captcha.captcha_id) appendAuthParams(params, { password, captcha })
if (captcha?.captcha_solution) params.append('captcha_solution', captcha.captcha_solution)
if (captcha?.captcha_token) params.append('captcha_token', captcha.captcha_token)
params.append('limit', limit.toString()) params.append('limit', limit.toString())
return apiRequest<DashboardData>(`/public/sites/${siteId}/dashboard?${params.toString()}`) return apiRequest<DashboardData>(`/public/sites/${siteId}/dashboard?${params.toString()}`)