24/1/29 pagenation (firebase)

2024. 1. 30. 08:36카테고리 없음

<ListPage.jsx> pagenation을 파이어베이스를 이용해 구현해 보았다.

"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import writeImage from "@/app/assets/images/icon/write_icon.png";
import userIcon from "../assets/images/icon/userIcon.png";
import { useQuery } from "@tanstack/react-query";
import { collection, getDocs, limit, orderBy, query, startAfter } from "firebase/firestore"; //여기서 새롭게 써본
import { db, storage } from "@/shared/firebase";                         //파이어베이스 메서드가 많다.
import { Post } from "../assets/types/types";
import { nanoid } from "nanoid";
import Link from "next/link";
import CategoryBtn from "@/components/community/CategoryBtn";
import { useRouter } from "next/navigation";
import CuteHeart from "@/components/community/CuteHeart";
import { getDownloadURL, ref } from "firebase/storage";
import PostCard from "@/components/mypage/PostCard";
import BestPost from "@/components/community/BestPost";
import { getHearts } from "@/components/community/Fns";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/config/configStore";
import moeumLoading from "../assets/images/moeumLoading.gif";

export default function ListPage() {
  const [newPost, setNewPost] = useState<Post>({   //게시글 담아둘 NewPost
    id: "",
    uid: "",
    title: "",
    content: "",
    profile: "",
    nickname: "",
    createdAt: 0,
    category: ""
  });

  const [top3Shops, setTop3Shops] = useState<(Post | undefined)[]>();  //페이지 네이션과 무관한
  const router = useRouter();                                                    //베스트글 3개 뽑기
  const { data: posts, isLoading } = useQuery({
    queryKey: ["posts"],                                                 //이 유즈쿼리는 단순히 모든 게시글을 불러온다.
    queryFn: () => {
      const getPosts = async () => {
        let data: Post[] = [];
        const response = await getDocs(collection(db, "posts"));
        response.forEach((post) => {
          const postData = post.data();
          data.push({ ...postData, id: post.id });
        });
        // setPostArray(data);
        return data;
      };

      return getPosts();
    }
  });
  const [postsArray, setPostArray] = useState<Post[] | undefined>(posts);  //일단 초기값을 유즈쿼리로 가져온
  const { data: hearts } = useQuery({                                            //값을 초기값으로 달아봤는데 작동안한다.
    queryKey: [`hearts`],                                                      //역시 setPostArray로 직접 바꿔주지 않는한 
    queryFn: getHearts                                                       //화면 변경은 안된다.
  });
  const filteredPosts = postsArray                                   //조건부로 화면을 보여주기위한 필터링
    ?.filter((post) => {
      if (newPost.category === "" || newPost.category === "전체모음") {
        return true;
      }
      return newPost.category === post.category;
    })
    .sort((a, b) => {
      if (a.createdAt && b.createdAt) {
        return b.createdAt - a.createdAt;         //속성에 있는 createdAt으로 내림차순
      }
      return 1;
    })
    .slice(0, 5);

  useEffect(() => {
    const uniqueArray = hearts?.map((item) => {   //페이지네이션과는 무관한로직
      return item.postId;
    });                                                                  //설명하자면 item의 포스트아이디를 갖는 배열을 새로 만들고
    // console.log(uniqueArray, "요거는?");
    const countThumbs: Record<string, number> = {};   //문자열또는 숫자를 요소로 갖는 객체
    uniqueArray?.forEach((element) => {
      const key = String(element);
      countThumbs[key] = (countThumbs[key] || 0) + 1;   //이제 위에 있던 배열을 돌며 해당배열의 요소를
    });                                                                              //키로 갖고 동일한 키에대한 count를 value로 갖는 객체 생성
    const entries = Object.entries(countThumbs);                  //그 키와 밸류 2개를 요소로갖는 배열을 가진
    const sortedEntries = entries.sort((a, b) => b[1] - a[1]); //객체를 생성해서 count의 값에따른
    const top3 = sortedEntries.slice(0, 3);                         // 내림차순으로 만든다. 그리고 앞에서 3개를 뽑는다.
    // console.log(top3, "탑3");
    const foundTop3 = top3.map((postId) => {   //이제 top3에 해당하는 post를 찾아낸다.
      return posts?.find((post) => {
        return post.id === postId[0];
      });
    });
    // console.log(foundTop3, "과연");
    if (foundTop3 !== undefined) {
      setTop3Shops(foundTop3);       //그 찾아낸 top3의 post를 useState에 저장 //참고로 페이지네이션과는무관
    }
  }, [hearts]);
  let BtnArray: number[] = [];         //페이지네이션을 위한 페이지 이동버튼 생성 로직
  if (posts?.length) {
    for (let i = 1; i <= Math.ceil(posts?.length / 5); i++) {   //페이지에 게시글을 5개씩 담을 예정
      BtnArray.push(i);                       //전체 posts의 길이를 5개로 나눈값의 소수첫번쨰에서 올림한값이
    }                                                 //필요한 페이지의 갯수이다. 그숫자들을 갖는 배열이 BTNArray
    // console.log(BtnArray, "어레이");
  }
  const pagenationHandler = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    const BtnNumber = e.currentTarget.innerText;        //여기는 페이지버튼을 눌렀을때 동작하는 로직
    if (BtnNumber === "1") {                                   //1번 버튼을 누르면
      let data2: Post[] = [];

      const postRef = collection(db, "posts");             //단순히 createdAt의 내림차순으로
      const postQuery = query(
        postRef,
        orderBy("createdAt", "desc"),

        limit(5)                                                       //가장앞에 5개의 데이터를 불러온다.
      );
      const snapshot = await getDocs(postQuery);

      snapshot.forEach((doc) => {
        data2.push({ ...doc.data(), id: doc.id });             //거기다 아이디 달아주고
      });
      setPostArray(data2);                                 //state에 저장하면 끝
    } else {
      let data2: Post[] = [];                          // 1번 이외의 버튼
      const first = query(collection(db, "posts"), orderBy("createdAt", "desc"), limit(5 * (Number(BtnNumber) - 1)));
      const documentSnapshots = await getDocs(first);                      //상대적 앞페이지들에대한 정보

      const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
                                                                                                           //앞페이지들에서 가장 끝에 Post정보
      const postRef = collection(db, "posts");
      const postQuery = query(postRef, orderBy("createdAt", "desc"), startAfter(lastVisible), limit(5));
      const snapshot = await getDocs(postQuery);                        //startAfter로 가장 끝 정보부터 뒤에 5개를 가져옴

      snapshot.forEach((doc) => {
        data2.push({ ...doc.data(), id: doc.id });
      });
      setPostArray(data2);                                       //그걸 화면에 뿌림
    }
  };

  if (isLoading)
    return (
      <div className="flex justify-center items-center w-full h-full">
        <Image src={moeumLoading} alt="loading" className="w-[300px] h-[300px]" />
      </div>
    );

  return (
    <>
      {/* 전체 컨테이너 */}
      <div className="flex  flex-col items-center  w-full gap-[20px]">
        {/* 글 모음/ 인기순위 컨텐츠 */}
        <div className="flex w-full py-[60px] flex-col justify-center items-center gap-[60px] bg-[#FAFAFA]">
          <div className="flex flex-col gap-[12px] items-center">
            <section>
              <h1 className="text-[28px] text-[#212121] font-semibold leading-[36px]">이 달의 BEST 게시글 모-음</h1>
              <h2 className="text-[18px] text-[#5C5C5C] leading-[26px]">가장 인기 많았던 게시글을 확인해보세요!</h2>
            </section>
          </div>

          {/* 인기순위 컨테이너 */}
          <div className="flex justify-center items-center gap-[24px] cursor-pointer max-sm:overflow-x-scroll max-sm:w-[350px] max-sm:justify-start max-sm:scrollbar-hide max-md:overflow-x-scroll max-md:w-[350px] max-md:justify-start max-lg:overflow-x-scroll max-lg:w-[680px] max-lg:justify-start">
            {top3Shops?.map((post) => {
              return <React.Fragment key={nanoid()}>{post && <BestPost post={post} />}</React.Fragment>;
            })}
          </div>
        </div>

        <div className="flex flex-col justify-center items-center gap-[60px] w-full pt-[60px] px-[20px]">
          <div className="">
            <div className="flex flex-col justify-center items-center gap-[40px]">
              <h2 className="text-[28px] text-[#212121] text-center font-semibold leading-[36px]">실시간모음</h2>
              <section className="grid max-sm:grid-cols-3 sm:grid-cols-5 gap-[16px] w-full">
                <CategoryBtn text="전체모음" type="" setNewPost={setNewPost} newPost={newPost} />
                <CategoryBtn text="일상이야기" type="" setNewPost={setNewPost} newPost={newPost} />
                <CategoryBtn text="맛집추천" type="" setNewPost={setNewPost} newPost={newPost} />
                <CategoryBtn text="취미생활" type="" setNewPost={setNewPost} newPost={newPost} />
                <CategoryBtn text="문의하기" type="" setNewPost={setNewPost} newPost={newPost} />
              </section>
            </div>
          </div>

          <div className="w-full flex justify-center">
            <div className="w-[680px] flex justify-end">
              <Link href="community/write">
                <button className=" flex h-[40px] px-[12px] py-[8px] justify-center items-center gap-[8px] rounded-[8px]   text-[white] bg-[#FF8145] hover:bg-[#E5743E] ">
                  <div className="w-[20px] h-[20px]">
                    <Image src={writeImage} alt="write"></Image>
                  </div>
                  <p className="text-[14px] leading-[20px] font-medium ">작성하기</p>
                </button>
              </Link>
            </div>
          </div>
          <div className="flex w-full flex-col items-center gap-[40px]">
            {/* 작성하기 버튼 */}

            {/* 게시글 전체 컨테이너 */}
            <div className="flex flex-col justify-center items-center w-full">
              {filteredPosts?.map((post) => {
                return (
                  <React.Fragment key={nanoid()}>
                    <PostCard post={post} />
                  </React.Fragment>
                );
              })}
            </div>
          </div>
        </div>
        <section className="flex gap-1 ">
          {BtnArray.map((item) => {
            return (
              <button
                key={nanoid()}
                className="mb-[60px] flex h-[40px] px-[12px] py-[8px] justify-center items-center gap-[8px] rounded-[8px]   text-[white] bg-[#FF8145] hover:bg-[#E5743E] "
                onClick={(e) => pagenationHandler(e)}
              >
                {item}
              </button>
            );
          })}
        </section>
      </div>
    </>
  );
}

//처음 해본 pagenation이라 이게 맞는지도 모르겠다. 개선안 적어주시면 감사합니다.