feat: add previous period statistics and enhance data loading in PublicDashboardPage
This commit is contained in:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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()}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user