From 8d9a3f35927655ffb89364c95780fd257fd539a5 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 23 Mar 2026 11:34:05 +0100 Subject: [PATCH] feat(pagespeed): add check history navigation with prev/next arrows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Navigate between historical checks using ◀ ▶ arrows in the hero footer bar. Shows formatted date when viewing historical data, "Last checked X ago" when on latest. Fetches full audit data via getPageSpeedCheck when navigating to a historical check. --- app/sites/[id]/pagespeed/page.tsx | 121 ++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 8 deletions(-) diff --git a/app/sites/[id]/pagespeed/page.tsx b/app/sites/[id]/pagespeed/page.tsx index ab0d09f..6ff082c 100644 --- a/app/sites/[id]/pagespeed/page.tsx +++ b/app/sites/[id]/pagespeed/page.tsx @@ -1,10 +1,10 @@ 'use client' import { useAuth } from '@/lib/auth/context' -import { useEffect, useState, useRef, useCallback } from 'react' +import { useEffect, useState, useRef, useCallback, useMemo } from 'react' import { useParams } from 'next/navigation' import { useSite, usePageSpeedConfig, usePageSpeedLatest, usePageSpeedHistory } from '@/lib/swr/dashboard' -import { updatePageSpeedConfig, triggerPageSpeedCheck, getPageSpeedLatest, type PageSpeedCheck, type AuditSummary } from '@/lib/api/pagespeed' +import { updatePageSpeedConfig, triggerPageSpeedCheck, getPageSpeedLatest, getPageSpeedCheck, type PageSpeedCheck, type AuditSummary } from '@/lib/api/pagespeed' import { toast, Button } from '@ciphera-net/ui' import { motion } from 'framer-motion' import ScoreGauge from '@/components/pagespeed/ScoreGauge' @@ -75,8 +75,80 @@ export default function PageSpeedPage() { const { data: historyChecks } = usePageSpeedHistory(siteId, strategy) - // * Get the check for the current strategy - const currentCheck = latestChecks?.find(c => c.strategy === strategy) ?? null + // * Check history navigation — build unique check timestamps from history data + const [selectedCheckId, setSelectedCheckId] = useState(null) + const [selectedCheckData, setSelectedCheckData] = useState(null) + const [loadingCheck, setLoadingCheck] = useState(false) + + // * Build unique check timestamps (each check has mobile+desktop at the same time) + const checkTimestamps = useMemo(() => { + if (!historyChecks?.length) return [] + const seen = new Set() + const timestamps: { id: string; checked_at: string }[] = [] + // * History is sorted ASC by checked_at, reverse for newest first + for (let i = historyChecks.length - 1; i >= 0; i--) { + const c = historyChecks[i] + // * Group by minute to deduplicate mobile+desktop pairs + const key = c.checked_at.slice(0, 16) + if (!seen.has(key)) { + seen.add(key) + timestamps.push({ id: c.id, checked_at: c.checked_at }) + } + } + return timestamps + }, [historyChecks]) + + const selectedIndex = selectedCheckId + ? checkTimestamps.findIndex(t => t.id === selectedCheckId) + : 0 // * 0 = latest + + const canGoPrev = selectedIndex < checkTimestamps.length - 1 + const canGoNext = selectedIndex > 0 + + const handlePrevCheck = () => { + if (!canGoPrev) return + const next = checkTimestamps[selectedIndex + 1] + setSelectedCheckId(next.id) + } + + const handleNextCheck = () => { + if (selectedIndex <= 1) { + // * Going back to latest + setSelectedCheckId(null) + setSelectedCheckData(null) + return + } + const next = checkTimestamps[selectedIndex - 1] + setSelectedCheckId(next.id) + } + + // * Fetch full check data when navigating to a historical check + useEffect(() => { + if (!selectedCheckId || !siteId) { + setSelectedCheckData(null) + return + } + let cancelled = false + setLoadingCheck(true) + getPageSpeedCheck(siteId, selectedCheckId).then(data => { + if (!cancelled) { + setSelectedCheckData(data) + setLoadingCheck(false) + } + }).catch(() => { + if (!cancelled) setLoadingCheck(false) + }) + return () => { cancelled = true } + }, [selectedCheckId, siteId]) + + // * Determine which check to display — selected historical or latest + const displayCheck = selectedCheckId && selectedCheckData + ? selectedCheckData + : latestChecks?.find(c => c.strategy === strategy) ?? null + + // * When viewing a historical check, we need both strategies — fetch the other one too + // * For simplicity, historical view shows the selected strategy's check + const currentCheck = displayCheck // * Set document title useEffect(() => { @@ -299,7 +371,7 @@ export default function PageSpeedPage() { {(['mobile', 'desktop'] as const).map(tab => ( + )} {currentCheck?.checked_at && ( - Last checked {formatTimeAgo(currentCheck.checked_at)} + + {selectedCheckId + ? new Date(currentCheck.checked_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }) + : `Last checked ${formatTimeAgo(currentCheck.checked_at)}` + } + + )} + {checkTimestamps.length > 1 && ( + )} {config?.frequency && ( {config.frequency} )} + {loadingCheck && ( + Loading... + )}
0–49