diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx index e8ad2f2..8d35693 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; -import { CardData } from '@/types/dashboardView'; +import { Cards } from '@/types/dashboardView'; import { useModal } from '@/app/(with-header-sidebar)/mydashboard/_hooks/useModal'; import Modal from '@/app/(with-header-sidebar)/mydashboard/_components/modal/Modal'; import CardInfo from './card-detail/CardInfo'; import styles from './Card.module.css'; interface Props { - item: CardData; + item: Cards; index: number; } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.module.css index a7a7e14..94c3bf3 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.module.css +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.module.css @@ -91,7 +91,22 @@ background: var(--violet-light); } +.scrollContext { + overflow-x: hidden; + overflow-y: auto; + max-height: calc(100vh - 124px); + height: auto; + display: flex; + flex-direction: column; +} + +.scrollContext::-webkit-scrollbar { + width: 0; +} + .dropContext { - height: 100%; - overflow: hidden; + min-height: auto; + display: flex; + flex-direction: column; + flex-grow: 1; } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx index 27bd8ee..6d62384 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { Droppable } from 'react-beautiful-dnd'; -import { ColumnData } from '@/types/dashboardView'; +import { Columns } from '@/types/dashboardView'; import Button from '@/components/Button'; import Image from 'next/image'; import Card from './Card'; @@ -8,15 +8,63 @@ import useModalStore from '@/store/modalStore'; import CreateTaskModal from './CreateTaskModal'; import styles from './Column.module.css'; -function Column({ color, title, totalCount, id, items }: ColumnData) { +function Column({ + color, + title, + totalCount, + id, + items, + loadMoreData, +}: Columns) { + const observerRef = useRef(null); + const loadMoreRef = useRef(null); + const columnRef = useRef(null); + const [minHeight, setMinHeight] = useState('auto'); const { openModal } = useModalStore(); + useEffect(() => { + observerRef.current = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting) { + loadMoreData(id); + } + }, + { + root: document.querySelector('.scrollContext'), + threshold: 0.4, + } + ); + + const loadMoreElement = loadMoreRef.current; + + if (totalCount === 0) return; + + if (loadMoreElement && observerRef.current && totalCount >= 10) { + observerRef.current.observe(loadMoreElement); + } + + return () => { + if (loadMoreElement && observerRef.current) { + observerRef.current.unobserve(loadMoreElement); + } + }; + }, [id, loadMoreData]); + + useEffect(() => { + if (items.length === 0 || items.length <= 5) { + setMinHeight('866px'); + } else { + setMinHeight('auto'); + } + }, [items]); + const handleCreateTask = () => { openModal(); }; return ( -
+
@@ -52,26 +100,32 @@ function Column({ color, title, totalCount, id, items }: ColumnData) {
- - {(provided) => ( -
- {items.map((item, index) => - item ? : null - )} - {provided.placeholder} -
- )} -
+
+ + {(provided) => ( +
+ {items.map((item, index) => + item ? : null + )} + + {provided.placeholder} + +
+
+ )} + +
); } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx index 56281cc..309ff37 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx @@ -1,10 +1,10 @@ -import type { CardData } from '@/types/dashboardView'; +import type { Cards } from '@/types/dashboardView'; import Avatar from '@/components/Avatar'; import { formatDateToCustomFormat } from '@/utils/dateUtils'; import styles from './Assignment.module.css'; interface AssignmentProps { - card: Pick; + card: Pick; } export default function Assignment({ card }: AssignmentProps) { diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx index 7dc4e97..bbe28f4 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx @@ -1,8 +1,8 @@ -import { CardData } from '@/types/dashboardView'; +import { Cards } from '@/types/dashboardView'; import Assignment from './Assignment'; import styles from './CardInfo.module.css'; -export default function CardInfo({ card }: { card: CardData }) { +export default function CardInfo({ card }: { card: Cards }) { return (
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/hooks/useDashBoardView.ts b/src/app/(with-header-sidebar)/dashboard/[id]/hooks/useDashBoardView.ts index d0f17d2..cca2745 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/hooks/useDashBoardView.ts +++ b/src/app/(with-header-sidebar)/dashboard/[id]/hooks/useDashBoardView.ts @@ -2,14 +2,14 @@ import { useState, useEffect, useCallback } from 'react'; import debounce from 'lodash/debounce'; import axiosInstance from '@/lib/axiosInstance'; import { DropResult } from 'react-beautiful-dnd'; -import { ColumnData, CardData } from '@/types/dashboardView'; +import { Columns, Cards } from '@/types/dashboardView'; import { COLUMN_URL, CARD_URL } from '@/constants/urls'; export default function useDashBoardView(dashboardId: string | undefined) { - const [columns, setColumns] = useState([]); + const [columns, setColumns] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [cursor, setCursor] = useState(null); + const [cursors, setCursors] = useState>({}); const fetchData = useCallback(async () => { setLoading(true); @@ -19,67 +19,58 @@ export default function useDashBoardView(dashboardId: string | undefined) { ); const columns = columnData.data; - - const columnIds: number[] = columns.map( - (column: ColumnData) => column.id - ); + const columnIds: number[] = columns.map((column: Columns) => column.id); const cardRequests = columnIds.map((columnId) => - axiosInstance.get( - `${CARD_URL}?size=10&columnId=${columnId}&cursor=${cursor || ''}` - ) + axiosInstance.get(`${CARD_URL}?size=10&columnId=${columnId}`) ); const cardResponses = await Promise.all(cardRequests); - console.log(cardResponses[0].data.totalCount); - - const updatedColumns = columns.map( - (column: ColumnData, index: number) => { - const cardData = cardResponses[index].data.cards; - const totalCount = cardResponses[index].data.totalCount; - return { - ...column, - items: cardData || [], - totalCount: totalCount || 0, - }; - } + + const updatedColumns = columns.map((column: Columns, index: number) => { + const cardData = cardResponses[index].data.cards; + const totalCount = cardResponses[index].data.totalCount; + + return { + ...column, + items: cardData || [], + totalCount: totalCount || 0, + }; + }); + + const initialCursors = updatedColumns.reduce( + (acc: Record, column: Columns) => { + const lastCard = column.items[column.items.length - 1]; + acc[column.id] = lastCard ? lastCard.id : null; + return acc; + }, + {} as Record ); + setColumns(updatedColumns); - setCursor(cardResponses[0].data.cursor); - } catch (err: unknown) { + setCursors(initialCursors); + } catch (err) { if (err instanceof Error) setError(err.message); } finally { setLoading(false); } - }, [dashboardId, cursor]); - //체크 + }, [dashboardId]); useEffect(() => { - console.log('렌더링 체크'); if (!dashboardId) return; - fetchData(); - }, [fetchData, dashboardId, cursor]); - //체크 + }, [fetchData, dashboardId]); const sendCardUpdateRequest = useCallback( - debounce(async (cardId: string, updatedCardData: CardData) => { + debounce(async (cardId: string, updatedCardData: Cards) => { try { - const response = await axiosInstance.put( - `${CARD_URL}/${cardId}`, - updatedCardData - ); - console.log('업뎃카드데이터', updatedCardData); - if (response.status === 200 || response.status === 204) { - console.log('카드가 성공적으로 업데이트되었습니다.'); - } - } catch (err: unknown) { + await axiosInstance.put(`${CARD_URL}/${cardId}`, updatedCardData); + } catch (err) { if (err instanceof Error) setError(err.message); } }, 200), [] ); - //체크 const handleOnDragEnd = useCallback( async (result: DropResult) => { @@ -126,24 +117,74 @@ export default function useDashBoardView(dashboardId: string | undefined) { updatedColumns[sourceColumnIndex].totalCount -= 1; updatedColumns[destinationColumnIndex].totalCount += 1; - console.log(updatedColumns[sourceColumnIndex].totalCount); + try { + await sendCardUpdateRequest(`${removed.id}`, { + ...removed, + columnId: removed.columnId, + }); + } catch (err) { + if (err instanceof Error) setError(err.message); + } } + const updatedCursors = updatedColumns.reduce( + (acc, column) => { + const lastCard = column.items[column.items.length - 1]; + acc[column.id] = lastCard ? lastCard.id : null; + return acc; + }, + {} as Record + ); + setColumns(updatedColumns); + setCursors(updatedCursors); + }, + [columns, sendCardUpdateRequest] + ); + const loadMoreData = useCallback( + debounce(async (columnId: number) => { try { - await sendCardUpdateRequest(`${removed.id}`, { - ...removed, - columnId: removed.columnId, - }); - } catch (err: unknown) { + const currentCursor = cursors[columnId]; + + if (currentCursor === null) return; + + const { data: cardResponses } = await axiosInstance.get( + `${CARD_URL}?size=10&cursorId=${currentCursor}&columnId=${columnId}` + ); + + const newCards = cardResponses.cards; + const newCursorId = cardResponses.cursorId; + + setColumns((prevColumns) => + prevColumns.map((column) => + column.id === columnId + ? { + ...column, + items: [ + ...column.items, + ...newCards.filter( + (newCard: Cards) => + !column.items.some( + (prevCard) => prevCard.id === newCard.id + ) + ), + ], + } + : column + ) + ); + + setCursors((prevCursors) => ({ + ...prevCursors, + [columnId]: newCursorId || null, + })); + } catch (err) { if (err instanceof Error) setError(err.message); } - }, - [columns, sendCardUpdateRequest] + }, 200), + [cursors] ); - //체크 - - return { columns, loading, error, handleOnDragEnd }; + return { columns, loading, error, handleOnDragEnd, loadMoreData }; } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx index 1eb5f58..4ac244b 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/page.tsx @@ -14,14 +14,12 @@ export default function DashBoardView() { const params = useParams(); const id = params.id; - const dashboard = useDashboardStore((state) => state.dashboard); - const setDashboard = useDashboardStore((state) => state.setDashboard); - const searchParams = useSearchParams(); const color = searchParams.get('color') || 'var(--violet)'; - const { columns, loading, error, handleOnDragEnd } = useDashBoardView( - id as string - ); + const { columns, loading, error, handleOnDragEnd, loadMoreData } = + useDashBoardView(`${id}`); + const dashboard = useDashboardStore((state) => state.dashboard); + const setDashboard = useDashboardStore((state) => state.setDashboard); useEffect(() => { if (dashboard?.id !== Number(id)) { @@ -43,6 +41,7 @@ export default function DashBoardView() { totalCount={column.totalCount} id={column.id} items={column.items} + loadMoreData={loadMoreData} /> ))}
diff --git a/src/types/dashboardView.ts b/src/types/dashboardView.ts index 4574308..616d62d 100644 --- a/src/types/dashboardView.ts +++ b/src/types/dashboardView.ts @@ -1,16 +1,18 @@ -export interface Assignee { +import { DebouncedFunc } from 'lodash'; + +export interface CardAssignee { profileImageUrl: string; nickname: string; id: number; } -export interface CardData { +export interface Cards { id: number; title: string; description: string; tags: string[]; dueDate: string; - assignee?: Assignee; + assignee: CardAssignee; imageUrl: string; teamId: string; columnId: number; @@ -18,16 +20,17 @@ export interface CardData { updatedAt: string; } -export interface CardResponse { +export interface GetCardsResponse { cursorId: number; totalCount: number; - cards: CardData[]; + cards: Cards[]; } -export interface ColumnData { +export interface Columns { title: string; color: string; totalCount: number; id: number; - items: CardData[]; + items: Cards[]; + loadMoreData: DebouncedFunc<(columnId: number) => Promise>; }