Skip to content

Commit a8b39dc

Browse files
authored
Merge pull request #101 from hyeonjiroh/feat/#88/task-pagenation
feat: #88/대시보드 상세 페이지 할 일 카드 무한 스크롤
2 parents 5e66c4a + ecc81ee commit a8b39dc

File tree

5 files changed

+93
-22
lines changed

5 files changed

+93
-22
lines changed

next.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const nextConfig = {
44
//로컬 이미지 테스트 하기 위해 localhost를 넣었습니다.
55
domains: ["sprint-fe-project.s3.ap-northeast-2.amazonaws.com", "localhost"],
66
},
7+
reactStrictMode: false,
78
};
89

910
export default nextConfig;

src/app/(after-login)/dashboard/[dashboardid]/_components/AddTaskButton.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use client";
2-
31
import { useModalStore } from "@/lib/store/useModalStore";
42
import Button from "@/components/common/button/Button";
53
import Image from "next/image";

src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,79 @@
1+
"use client";
2+
3+
import { useEffect, useState, useRef } from "react";
14
import { DashboardColumn, TaskCardList } from "@/lib/types";
25
import { fetchTaskCardList } from "@/lib/apis/cardsApi";
36
import { TOKEN_1 } from "@/lib/constants/tokens";
47
import EditColumnButton from "./EditColumnButton";
58
import AddTaskButton from "./AddTaskButton";
69
import TaskCard from "./TaskCard";
710

8-
export default async function Column({ id, title }: DashboardColumn) {
9-
const { cards, totalCount, cursorId } = await fetchTaskCardList({
10-
token: TOKEN_1,
11-
id: id,
12-
});
13-
const items: TaskCardList[] = cards;
11+
const PAGE_SIZE = 3;
12+
13+
export default function Column({ id, title }: DashboardColumn) {
14+
const [items, setItems] = useState<TaskCardList[]>([]);
15+
const [cursorId, setCursorId] = useState<number | null>(null);
16+
const [totalCount, setTotalCount] = useState(0);
17+
const [isLoading, setIsLoading] = useState(false);
18+
const [isLast, setIsLast] = useState(false);
19+
const observerRef = useRef<HTMLDivElement | null>(null);
20+
21+
const handleLoad = async () => {
22+
if (isLoading || isLast) return;
23+
setIsLoading(true);
24+
25+
try {
26+
const {
27+
cards: newCards,
28+
cursorId: nextCursorId,
29+
totalCount,
30+
} = await fetchTaskCardList({
31+
token: TOKEN_1,
32+
size: PAGE_SIZE,
33+
cursorId,
34+
columnId: id,
35+
});
36+
37+
setItems((prev) => [...prev, ...newCards]);
38+
setCursorId(nextCursorId);
39+
setTotalCount(totalCount);
40+
41+
if (newCards.length < PAGE_SIZE || nextCursorId === null) {
42+
setIsLast(true);
43+
}
44+
} finally {
45+
setIsLoading(false);
46+
}
47+
};
48+
49+
useEffect(() => {
50+
handleLoad();
51+
}, []);
1452

15-
// 해당 값 사용하게 되면(페이지네이션) 지울 테스트 코드
16-
console.log(cursorId);
53+
useEffect(() => {
54+
if (isLast) return;
55+
56+
const observer = new IntersectionObserver(
57+
(entries) => {
58+
if (entries[0].isIntersecting) {
59+
handleLoad();
60+
}
61+
},
62+
{ threshold: 0.5 }
63+
);
64+
65+
const current = observerRef.current;
66+
if (current) observer.observe(current);
67+
68+
return () => {
69+
if (current) observer.unobserve(current);
70+
observer.disconnect();
71+
};
72+
}, [cursorId, isLoading, isLast]);
1773

1874
return (
19-
<div className="py-4 border-b border-gray-300 tablet:px-5 tablet:pt-[22px] tablet:pb-5 pc:border-b-0 pc:border-r">
20-
<div className="flex flex-col gap-6 tablet:gap-[25px]">
75+
<div className="h-full py-4 border-b border-gray-300 tablet:px-5 tablet:pt-[22px] tablet:pb-5 pc:border-b-0 pc:border-r">
76+
<div className="flex flex-col gap-6 h-full tablet:gap-[25px]">
2177
<div className="flex justify-between">
2278
<div className="flex gap-3 items-center">
2379
<div className="flex gap-2 items-center">
@@ -32,11 +88,20 @@ export default async function Column({ id, title }: DashboardColumn) {
3288
</div>
3389
<EditColumnButton columnId={id} columnTitle={title} />
3490
</div>
35-
<div className="flex flex-col gap-[10px] tablet:gap-4">
36-
<AddTaskButton />
37-
{items.map((item) => (
38-
<TaskCard key={item.id} {...item} columnTitle={title} />
39-
))}
91+
<div className="flex flex-col gap-[10px] flex-grow min-h-0 tablet:gap-4">
92+
<div>
93+
<AddTaskButton />
94+
</div>
95+
<div className="flex flex-col gap-[10px] flex-grow min-h-0 overflow-y-auto whitespace-nowrap scrollbar-hide tablet:gap-4">
96+
{items.map((item, index) => (
97+
<div
98+
key={item.id}
99+
ref={index === items.length - 1 ? observerRef : null}
100+
>
101+
<TaskCard {...item} columnTitle={title} />
102+
</div>
103+
))}
104+
</div>
40105
</div>
41106
</div>
42107
</div>

src/app/(after-login)/dashboard/[dashboardid]/_components/EditColumnButton.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use client";
2-
31
import { useModalStore } from "@/lib/store/useModalStore";
42
import { useColumnStore } from "@/lib/store/useColumnStore";
53
import Image from "next/image";

src/lib/apis/cardsApi.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ import { BASE_URL } from "@/lib/constants/urls";
22

33
export async function fetchTaskCardList({
44
token,
5-
id,
5+
size,
6+
cursorId,
7+
columnId,
68
}: {
79
token: string;
8-
id: number;
10+
size: number;
11+
cursorId: number | null;
12+
columnId: number;
913
}) {
10-
const res = await fetch(`${BASE_URL}/cards?size=10&columnId=${id}`, {
14+
let query = `size=${size}&columnId=${columnId}`;
15+
if (cursorId !== null) {
16+
query += `&cursorId=${cursorId}`;
17+
}
18+
19+
const res = await fetch(`${BASE_URL}/cards?${query}`, {
1120
headers: {
1221
Accept: "application/json",
1322
Authorization: `Bearer ${token}`,

0 commit comments

Comments
 (0)