diff --git a/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx b/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx index 73f9eb54..ed7191e3 100644 --- a/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx +++ b/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx @@ -1,4 +1,5 @@ -import {useContext} from "react"; +import {useContext, useEffect, useRef, useState, useCallback} from "react"; +import {createPortal} from "react-dom"; import {ConfigContext} from "@/common/contexts/Config"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; import Speedtest from "../Speedtest"; @@ -6,41 +7,162 @@ import {getIconBySpeed} from "@/common/utils/TestUtil"; import "./styles.sass"; import {t} from "i18next"; -function TestArea() { +const TestArea = () => { const config = useContext(ConfigContext)[0]; - const [speedtests] = useContext(SpeedtestContext); + const {speedtests, loadMoreTests, loading, hasMore} = useContext(SpeedtestContext); + const [stickyDate, setStickyDate] = useState(null); + const [showStickyDate, setShowStickyDate] = useState(false); + const [initialLoadComplete, setInitialLoadComplete] = useState(false); + const containerRef = useRef(); + const lastElementRef = useRef(); + + useEffect(() => { + if (!loading && !initialLoadComplete) { + setInitialLoadComplete(true); + } + }, [loading, initialLoadComplete]); + + useEffect(() => { + if (speedtests.length > 0) { + const initialDate = getDateFromTest(speedtests[0]); + setStickyDate(initialDate); + } + }, [speedtests]); + + const getDateFromTest = (test) => { + const date = new Date(Date.parse(test.created)); + return date.toLocaleDateString("default", {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'}); + }; + + const handleScroll = useCallback(() => { + const scrollTop = Math.max(window.scrollY, document.documentElement.scrollTop, document.body.scrollTop); + + const shouldShow = scrollTop > 50; + setShowStickyDate(shouldShow); + + const windowHeight = window.innerHeight; + const documentHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, + document.documentElement.clientHeight, document.documentElement.scrollHeight, + document.documentElement.offsetHeight); + + const nearBottom = scrollTop + windowHeight >= documentHeight - 500; + const atBottom = scrollTop + windowHeight >= documentHeight - 50; + + if ((nearBottom || atBottom) && hasMore && !loading && speedtests.length > 0) { + loadMoreTests(); + } + + if (shouldShow && speedtests.length > 0) { + const testElements = document.querySelectorAll('.speedtest'); + if (testElements.length > 0) { + for (let i = 0; i < testElements.length; i++) { + const element = testElements[i]; + const elementRect = element.getBoundingClientRect(); + + if (elementRect.top <= 200 && elementRect.bottom > 50) { + if (speedtests[i]) { + const newDate = getDateFromTest(speedtests[i]); + setStickyDate(newDate); + } + break; + } + } + } + } + }, [speedtests, stickyDate, hasMore, loading, loadMoreTests]); + + useEffect(() => { + let ticking = false; + + const throttledScrollHandler = () => { + if (!ticking) { + requestAnimationFrame(() => { + handleScroll(); + ticking = false; + }); + ticking = true; + } + }; + + window.addEventListener('scroll', throttledScrollHandler, {passive: true}); + document.body.addEventListener('scroll', throttledScrollHandler, {passive: true}); + + setTimeout(handleScroll, 100); + + return () => { + window.removeEventListener('scroll', throttledScrollHandler); + document.body.removeEventListener('scroll', throttledScrollHandler); + }; + }, [handleScroll]); + + useEffect(() => { + if (!lastElementRef.current || !hasMore || loading) return; + + const observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasMore && !loading) loadMoreTests(); + }, {threshold: 0.01, rootMargin: '200px 0px'}); + + observer.observe(lastElementRef.current); + + return () => { + observer.disconnect(); + }; + }, [hasMore, loading, loadMoreTests, speedtests.length]); if (Object.entries(config).length === 0) return (<>>); - if (speedtests.length === 0) return
{t("test.loading_more")}
+{t("test.no_more_tests")}
+