Skip to content

Commit

Permalink
Merge pull request #404 from bettersg/329-infinite-scroll-for-voting-…
Browse files Browse the repository at this point in the history
…page-rather-than-current-5-per-page-method

329 infinite scroll for voting page rather than current 5 per page method
  • Loading branch information
sarge1989 authored Aug 17, 2024
2 parents 8dd36a8 + bf3b830 commit 9d79ea1
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 149 deletions.
2 changes: 1 addition & 1 deletion checkers-app/src/components/common/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function BackButton() {
const regex = /^\/messages\/[^/]+\/voteRequests\/[^/]+\/?$/;
function onClick() {
if (regex.test(location.pathname)) {
navigate("/votes");
navigate("/votes", { state: location.state }); //for resumption of old position
} else {
navigate(-1);
}
Expand Down
5 changes: 3 additions & 2 deletions checkers-app/src/components/common/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function Layout({
showMenu = false,
}: LayoutProps) {
return (
<div className="dark:bg-dark-background-color min-h-screen overflow-x-hidden">
<div className="dark:bg-dark-background-color h-screen">
<style>
{`
::-webkit-scrollbar {
Expand All @@ -24,9 +24,10 @@ export default function Layout({
</style>
{/* <div className='layout-padding'> */}
<Header pageName={pageName} showMenu={showMenu} />

{/* <PageHeader>{pageHeader}</PageHeader>
</div> */}
<div className="pb-16 mt-1">{children}</div>
<div className=" pb-16 mt-1">{children}</div>
<NavbarDefault />
</div>
);
Expand Down
8 changes: 5 additions & 3 deletions checkers-app/src/components/myvotes/MessageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./MessageCard.css";
interface MessageCardProps {
voteSummary: VoteSummary;
status: string;
scrollPosition: number;
}

type ColourMap = {
Expand Down Expand Up @@ -71,13 +72,14 @@ export default function MessageCard(props: MessageCardProps) {
firestorePath,
} = props.voteSummary;
const status = props.status;
const scrollPosition = props.scrollPosition;
// const colour: string = colours[category];
const navigate = useNavigate();
const dateString = dateToDateString(new Date(createdTimestamp));

// If the message is PENDING, clicking the button should go to the voting page
const viewVote = (firestorePath: string) => {
navigate(`/${firestorePath}`);
const viewVote = (firestorePath: string, status: string, scrollPosition: number) => {
navigate(`/${firestorePath}`, {state: {status: status, scrollPosition: scrollPosition}});
};

const textStyle = "font-normal"; //add bold in future
Expand Down Expand Up @@ -128,7 +130,7 @@ export default function MessageCard(props: MessageCardProps) {
return (
<div
className="flex border-b border-gray-500 h-16 hover-shadow dark:bg-dark-background-color"
onClick={() => viewVote(firestorePath)}
onClick={() => viewVote(firestorePath, status, scrollPosition)}
>
{/* Coloured dot if needs review*/}

Expand Down
186 changes: 117 additions & 69 deletions checkers-app/src/components/myvotes/MessagesDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,100 +16,148 @@ Idea:
- Have a nested component for each button
*/

import { useState, useEffect, FC } from "react";
interface MessagesDisplayProps {
status: "pending" | "voted";
scrollPosition: number;
}

import { useState, useEffect, FC, useCallback, useRef } from "react";
import { useUser } from "../../providers/UserContext";
import Loading from "../common/Loading";
import MessageCard from "./MessageCard";
import { Typography } from "@material-tailwind/react";
import { getCheckerVotes } from "../../services/api";
import { VoteSummary, VoteSummaryApiResponse } from "../../types";
import Pagination from "./Pagination"; // Make sure to create this component

const MessagesDisplay: FC = () => {
const MessagesDisplay: FC<MessagesDisplayProps> = ({
status,
scrollPosition,
}) => {
const { checkerDetails } = useUser();
const [isLoading, setIsLoading] = useState(false);
const [votes, setVotes] = useState<VoteSummary[]>([]);
const [lastPath, setLastPath] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<"pending" | "voted">("pending");
const [currentPage, setCurrentPage] = useState<number>(1);
const [activeTab, setActiveTab] = useState<"pending" | "voted">(status);
const [totalPages, setTotalPages] = useState<number>(1);
const [error, setError] = useState<string>("");
const [page, setPage] = useState<number>(1);
const [scrollY, setScrollY] = useState<number>(0);

const scrollRef = useRef<HTMLDivElement>(null);

// Scroll Functions
const handleScroll = () => {
const scrollY = window.scrollY;
setScrollY(scrollY);
};

useEffect(() => {
const fetchMessages = async () => {
setIsLoading(true);
try {
if (!checkerDetails.checkerId) {
throw new Error("Checker Id missing.");
}
const response: VoteSummaryApiResponse = await getCheckerVotes(
checkerDetails.checkerId,
activeTab.toLowerCase(),
5,
lastPath
);
// Assuming your API correctly maps to the ApiResponse interface
if (response.votes) {
setVotes(response.votes);
}
setTotalPages(response.totalPages);
setLastPath(response.lastPath);
setIsLoading(false);
} catch (err) {
setError("Failed to fetch messages");
setIsLoading(false);
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);

useEffect(() => {
if (scrollY >= scrollPosition) {
return;
} else if (scrollPosition !== 0) {
setTimeout(() => {
window.scrollTo({
top: scrollPosition,
behavior: "smooth",
});
}, 200);
}
}, [votes]);

const fetchMessages = async () => {
setIsLoading(true);
try {
if (!checkerDetails.checkerId) {
throw new Error("Checker Id missing.");
}
const response: VoteSummaryApiResponse = await getCheckerVotes(
checkerDetails.checkerId,
activeTab.toLowerCase(),
10,
lastPath
);
if (response.votes) {
setVotes((prevVotes) => [...prevVotes, ...response.votes]);
}
setTotalPages(response.totalPages);
setLastPath(response.lastPath);
setIsLoading(false);
} catch (err) {
setError("Failed to fetch messages");
setIsLoading(false);
}
};

useEffect(() => {
if (checkerDetails.checkerId) {
fetchMessages();
}
}, [checkerDetails.checkerId, activeTab, currentPage]);
}, [checkerDetails.checkerId, activeTab, page]);

const handleTabChange = (tab: "pending" | "voted") => {
setVotes([]);
setActiveTab(tab);
handlePageChange(1); // Reset to the first page whenever the tab changes
};

const observer = useRef<IntersectionObserver | null>(null);
// Function to use the Intersection Observer API
const lastMessageElementRef = useCallback(
(node: any) => {
if (isLoading) return;
if (observer.current) observer.current.disconnect();

observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && page !== totalPages) {
setPage((prevPage) => prevPage + 1);
}
});
if (node) observer.current.observe(node);
},
[isLoading]
);

// Function to handle page change from the Pagination component
const handlePageChange = (page: number) => {
setCurrentPage(page);
setPage(page);
if (page === 1) {
setLastPath(null);
}
};

if (isLoading) {
return <Loading />;
}

return (
<div>
<div className="flex justify-around relative shadow-md shadow-primary-color2/50">
<button
className={`w-1/2 text-center py-2 ${
activeTab === "pending"
? "border-b-2 border-primary-color2 text-primary-color2"
: "text-gray-400"
} font-bold`}
onClick={() => handleTabChange("pending")}
>
PENDING
</button>
<button
className={`w-1/2 text-center py-2 ${
activeTab === "voted"
? "border-b-2 border-primary-color2 text-primary-color2"
: "text-gray-400"
} font-bold`}
onClick={() => handleTabChange("voted")}
>
VOTED
</button>
<div className="sticky top-0 bg-white z-10">
<div className="flex justify-around relative shadow-md shadow-primary-color2/5">
<button
className={`w-1/2 text-center py-2 ${
activeTab === "pending"
? "border-b-2 border-primary-color2 text-primary-color2"
: "text-gray-400"
} font-bold`}
onClick={() => handleTabChange("pending")}
>
PENDING
</button>
<button
className={`w-1/2 text-center py-2 ${
activeTab === "voted"
? "border-b-2 border-primary-color2 text-primary-color2"
: "text-gray-400"
} font-bold`}
onClick={() => handleTabChange("voted")}
>
VOTED
</button>
</div>
</div>
<div
className="overflow-auto min-h-full"
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
>
<div className="flex-grow overflow-scroll" ref={scrollRef}>
{error && <div>{error}</div>}
{!error && votes.length === 0 && (
<div className="text-primary-color h-full flex justify-center pt-16">
Expand All @@ -118,18 +166,18 @@ const MessagesDisplay: FC = () => {
)}
{!error &&
votes.map((voteSummary, index) => (
<div key={index}>
<MessageCard voteSummary={voteSummary} status={activeTab} />
<div
key={index}
ref={votes.length === index + 1 ? lastMessageElementRef : null}
>
<MessageCard
voteSummary={voteSummary}
status={activeTab}
scrollPosition={scrollY}
/>
</div>
))}
</div>
{!error && votes.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
)}
</div>
);
};
Expand Down
73 changes: 0 additions & 73 deletions checkers-app/src/components/myvotes/Pagination.tsx

This file was deleted.

Loading

0 comments on commit 9d79ea1

Please sign in to comment.