Skip to content

Commit

Permalink
✨ feat(#13): 컬럼 내 카드 무한스크롤 적용
Browse files Browse the repository at this point in the history
- 10개를 기본으로 로드하고, 이후로는 1개씩 로드
- 로드 중인 카드는 로딩 스피너로 대체
- getCardsList() 서비스 함수 로직 수정
  • Loading branch information
wayandway committed Jul 8, 2024
1 parent 77181d3 commit a3ab903
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 11 deletions.
82 changes: 73 additions & 9 deletions src/containers/dashboard/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Image from 'next/image';
import { useEffect, useRef, useCallback, useState } from 'react';
import { Draggable, Droppable } from 'react-beautiful-dnd';

import Card from './Card';
Expand All @@ -7,7 +8,7 @@ import ColumnSkeleton from './ColumnSkeleton';
import useFetchData from '@/hooks/useFetchData';
import useModal from '@/hooks/useModal';
import { getCardsList } from '@/services/getService';
import { Card as CardType } from '@/types/Card.interface';
import { Card as CardType, CardsListResponse } from '@/types/Card.interface';
import { Column as ColumnType } from '@/types/Column.interface';

interface ColumnProps {
Expand All @@ -18,15 +19,69 @@ interface ColumnProps {

function Column({ column, index, columns }: ColumnProps) {
const { openModifyColumnModal, openEditCardModal, openTodoCardModal } = useModal();
const { data: cardsData, isLoading } = useFetchData<{ cards: CardType[] }>(['cards', column.id], () =>
getCardsList(column.id),
const [cards, setCards] = useState<CardType[]>([]);
const [totalCount, setTotalCount] = useState<number>(0);
const [cursorId, setCursorId] = useState<number | 0>(0);
const [isFetching, setIsFetching] = useState(false);
const observer = useRef<IntersectionObserver | null>(null);

const {
data: initialData,
isLoading,
error,
} = useFetchData<CardsListResponse>(['cards', column.id], () => getCardsList(column.id, 10));

useEffect(() => {
if (initialData) {
setCards(initialData.cards);
setTotalCount(initialData.totalCount);
setCursorId(initialData.cursorId || 0);
}
}, [initialData]);

const fetchCards = useCallback(
async (size: number, cursorId?: number) => {
setIsFetching(true);
try {
const response = await getCardsList(column.id, size, cursorId);
if (response.data.cards.length > 0) {
setCards((prevCards) => [...prevCards, ...response.data.cards]);
setCursorId(response.data.cursorId || 0);
} else {
setCursorId(0);
}
} catch (error) {
console.error('Error fetching cards:', error);
} finally {
setIsFetching(false);
}
},
[column.id],
);

const cards = cardsData?.cards || [];
const lastCardRef = useCallback(
(node: HTMLDivElement | null) => {
if (isFetching) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && cursorId !== 0) {
fetchCards(1, cursorId);
}
});
if (node) observer.current.observe(node);
},
[isFetching, cursorId, fetchCards],
);

return isLoading ? (
<ColumnSkeleton />
) : (
if (isLoading) {
return <ColumnSkeleton />;
}

if (error) {
return <div>Error loading cards: {error.message}</div>;
}

return (
<div className='block lg:flex'>
<div className='flex flex-col bg-gray-fa p-5 transition-colors lg:w-[354px] dark:bg-dark-bg'>
{/* Column Header */}
Expand All @@ -35,7 +90,7 @@ function Column({ column, index, columns }: ColumnProps) {
<span className='mr-[8px] text-xs text-violet'>𒊹</span>
<h2 className='mr-[12px] text-lg font-bold text-black-33 dark:text-dark-10'>{column.title}</h2>
<span className='flex size-[20px] items-center justify-center rounded-[6px] bg-gray-ee text-xs text-gray-78 dark:bg-dark-200 dark:text-dark-10'>
{cards.length}
{totalCount}
</span>
</div>

Expand Down Expand Up @@ -74,7 +129,10 @@ function Column({ column, index, columns }: ColumnProps) {
<Draggable key={`card-${card.id}`} draggableId={`card-${card.id}`} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
ref={(cardRef) => {
provided.innerRef(cardRef);
if (index === cards.length - 1) lastCardRef(cardRef);
}}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={() => {
Expand All @@ -89,6 +147,12 @@ function Column({ column, index, columns }: ColumnProps) {
)}
</Draggable>
))}
{isFetching &&
Array.from({ length: 1 }).map((card, index) => (
<div key={index} className='align-center py-3 opacity-50'>
<Image src='/icons/spinner.svg' alt='스피너 아이콘' width={20} height={20} />
</div>
))}
{provided.placeholder}
</div>
)}
Expand Down
10 changes: 8 additions & 2 deletions src/services/getService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ export const getInvitationsList = async (size: number = 10, cursorId?: number, t
};

// 카드 목록 조회
export const getCardsList = async (columnId: number) => {
return await instance.get(`/cards?columnId=${columnId}`);
export const getCardsList = async (columnId: number, size: number = 10, cursorId?: number) => {
const params = new URLSearchParams();
params.append('columnId', columnId.toString());
params.append('size', size.toString());
if (cursorId) {
params.append('cursorId', cursorId.toString());
}
return await instance.get(`/cards`, { params });
};

// 상세 카드 조회
Expand Down

0 comments on commit a3ab903

Please sign in to comment.