Skip to content

Commit

Permalink
Merge pull request #29 from eunji-0623/장재혁
Browse files Browse the repository at this point in the history
컬럼목록, 카드 목록 가져오는 api 적용, 컬럼카드 무한스크롤 적용
  • Loading branch information
juan0444 authored Jun 12, 2024
2 parents ff45af3 + a0ffc40 commit 1ccd6a2
Show file tree
Hide file tree
Showing 13 changed files with 423 additions and 139 deletions.
13 changes: 9 additions & 4 deletions src/api/cards/apiCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export interface GetCardListResponse {
cards: CardOverAll[];
}

export interface GetCardListQuery {
size?: number;
cursorId?: number | null;
columnId?: number;
}

// 여기부터 api 함수 선언부분입니다. **************************

// 카드 생성 api
Expand All @@ -60,15 +66,14 @@ export async function apiCreateCard(
// 카드 목록 가져오기 api
// size, cursorId, columnId를 파라미터로 받습니다.
export async function apiGetCardList(
size: number,
cursorId: number,
columnId: number,
query: GetCardListQuery,
): Promise<GetCardListResponse> {
const { size, columnId, cursorId } = query;
const res = await instance.get<GetCardListResponse>('/cards', {
params: {
size,
cursorId,
columnId,
cursorId,
},
});
return handleResponse(res);
Expand Down
44 changes: 15 additions & 29 deletions src/api/columns/apiColumns.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import instance from '../axiosInstance';
import { handleResponse } from '../errorHandler';

export interface ColumnOverAll {
id: number;
title: string;
createdAt: string;
updatedAt: string;
}

export interface CreateColumnBody {
title: string;
dashboardId: number;
Expand All @@ -21,23 +14,19 @@ export interface UploadCardImageBody {
image: string;
}

interface ColumnOverAllResponse {
status: number;
data: ColumnOverAll;
export interface ColumnOverAll {
id: number;
title: string;
createdAt: string;
updatedAt: string;
}

interface GetColumnResponse {
status: number;
result?: 'SUCCESS';
data: {
cursorId: number;
totalCount: number;
dashboards: ColumnOverAll[];
};
export interface GetColumnListResponse {
result: string;
data: ColumnOverAll[];
}

interface UploadCardImageResponse {
status: number;
export interface UploadCardImageResponse {
imageUrl: string;
}

Expand All @@ -46,17 +35,17 @@ interface UploadCardImageResponse {
// 컬럼 생성 api
export async function apiCreateColumn(
body: CreateColumnBody,
): Promise<ColumnOverAllResponse> {
const res = await instance.post<ColumnOverAllResponse>('/columns', body);
): Promise<ColumnOverAll> {
const res = await instance.post<ColumnOverAll>('/columns', body);
return handleResponse(res);
}

// 컬럼 목록 조회 api
// dashboardId를 파라미터로 받습니다.
export async function apiGetColumnList(
dashboardId: number,
): Promise<GetColumnResponse> {
const res = await instance.get<GetColumnResponse>('/columns', {
): Promise<GetColumnListResponse> {
const res = await instance.get<GetColumnListResponse>('/columns', {
params: {
dashboardId,
},
Expand All @@ -69,11 +58,8 @@ export async function apiGetColumnList(
export async function apiUpdateColumn(
body: UpdateColumnBody,
columnId: number,
): Promise<ColumnOverAllResponse> {
const res = await instance.put<ColumnOverAllResponse>(
`/columns/${columnId}`,
body,
);
): Promise<ColumnOverAll> {
const res = await instance.put<ColumnOverAll>(`/columns/${columnId}`, body);
return handleResponse(res);
}

Expand Down
40 changes: 40 additions & 0 deletions src/pages/dashboard.{dashboardid}/components/CardList/CardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ReactEventHandler } from 'react';
import ColumnCard from '../ColumnCard/ColumnCard';
import { CardOverAll } from '../../../../api/apiModule';

interface CardListProps {
cardList: CardOverAll[];
hasNext: boolean;
columnData: {
userId: number;
columnId: number;
dashboardId: number;
};
setElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
handleCardOnclick: ReactEventHandler;
}

function CardList({
cardList,
hasNext,
columnData,
setElement,
handleCardOnclick,
}: CardListProps) {
return (
<div>
{cardList.map((cardData) => (
<ColumnCard
key={cardData.id}
cardId={cardData.id}
cardData={cardData}
columnData={columnData}
handleCardOnclick={handleCardOnclick}
/>
))}
{hasNext && <div ref={setElement} style={{ height: '20px' }} />}
</div>
);
}

export default CardList;
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
position: relative;
flex-flow: column;
border: 1px solid #eee;
height: 100vh;
flex-shrink: 0;
overflow: scroll;

@include desktop {
height: 94.6vh;
flex-shrink: 0;
}
}

.columnHeaderContainer {
Expand Down
164 changes: 140 additions & 24 deletions src/pages/dashboard.{dashboardid}/components/Column/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,162 @@
import { useEffect, useRef, useState } from 'react';
import { AxiosError } from 'axios';
import styles from './Column.module.scss';
import ColumnCard from '../ColumnCard/ColumnCard';
import ColumnHeader from '../ColumnHeader/ColumnHeader';
import { AddNewTaskBtn } from '../../../../components/Btn/Btn';
import { apiGetCardList, CardOverAll } from '../../../../api/apiModule';
import useInfiniteScroll from '../../../../hooks/pagination/useInfiniteScroll';
import CardList from '../CardList/CardList';
import EditColumnManagement from '../../../modal/EditColumnManagement/EditColumnManagement';
import NewTodoModal from '../../../modal/NewTodoModal/NewTodoModal';

interface ColumnProps {
title: string;
columnId: number;
dashboardId: number;
userId: number;
}
interface ColumnData {
userId: number;
columnId: number;
dashboardId: number;
}

function Column({
title,
columnId,
dashboardId,
userId,
}: ColumnProps) {
const [cardList, setCardList] = useState<CardOverAll[]>([]);
const [columnData, setColumnData] = useState<ColumnData>({
userId: 0,
columnId: 0,
dashboardId: 0,
});
const [cursor, setCursor] = useState<number | null>(null);
const [totalCount, setTotalCount] = useState<number>(0);
const [hasNext, setHasNext] = useState(false);
const [errorState, setErrorState] = useState<string | null>(null);
const [settingModalOpen, setSettingModalOpen] = useState<boolean>(false);
const [addCardModalOpen, setAddCardModalOpen] = useState<boolean>(false);

// 스크롤 관련 ref
const columnRef = useRef<HTMLDivElement>(null);

function Column({ title }: ColumnProps) {
const handleAddTaskBtn = () => {};
// 다음에 불러오는 카드 수
const PAGE_SIZE = 5;

const CardProps = {
assignee: 'unknown',
title: 'title',
dueDate: new Date('1915-08-15 19:20:30'),
tags: ['프론트', '프로젝트', '월요일', '좋아'],
// 처음 카드 목록을 조회
useEffect(() => {
const getFirstCardList = async () => {
try {
const res = await apiGetCardList({ size: 10, columnId });
const firstList = res.cards;
setCursor(res.cursorId);
setTotalCount(res.totalCount);
setCardList(firstList);
if (firstList.length < 10) {
setHasNext(false);
} else setHasNext(true);
} catch (error) {
const axiosError = error as AxiosError;
setErrorState(axiosError.message || '목록을 가져오는데 실패했습니다');
}
};

getFirstCardList();
}, [columnId]);

// 이후의 카드 목록을 조회
const getMoreCardList = async () => {
try {
const res = await apiGetCardList({
size: PAGE_SIZE,
cursorId: cursor,
columnId,
});
const moreList = res.cards;
setCursor(res.cursorId);
setTotalCount(res.totalCount);
setCardList((prevList) => [...prevList, ...moreList]);
if (moreList.length < PAGE_SIZE) setHasNext(false);
else setHasNext(true);
} catch (error) {
const axiosError = error as AxiosError;
setErrorState(axiosError.message || '목록을 가져오는데 실패했습니다');
}
};

// 무한스크롤 구현용 컴포넌트
const setElement = useInfiniteScroll(getMoreCardList, {
root: null,
rootMargin: '50px',
threshold: 0.5,
});

// 컬럼 데이터 prop 전달용
useEffect(() => {
setColumnData({
userId,
columnId,
dashboardId,
});
}, [userId, columnId, dashboardId]);

// 버튼 이벤트 핸들러

const scrollToTop = () => {
if (columnRef.current) {
// 스크롤을 맨 위로 초기화
columnRef.current.scrollTop = 0;
}
};
const handleSettingOnClick = () => {
setSettingModalOpen(!settingModalOpen);
};
const CardProps2 = {
assignee: 'unknown',
title: 'title',
dueDate: new Date('1915-08-15 19:20:30'),
tags: ['프론트', '백엔드', '프로젝트', '월요일', '좋아'],
imageUrl: '/img/test_img.png',
const handleAddTaskBtn = () => {
setAddCardModalOpen(!addCardModalOpen);
};

// 컴포넌트 출력
return (
<div className={styles.container}>
<div>{errorState}</div>
<div className={styles.columnHeaderContainer}>
<ColumnHeader name={title} />
<ColumnHeader
name={title}
totalNum={totalCount}
scrollToTop={scrollToTop}
handleSettingOnClick={handleSettingOnClick}
/>
</div>
<div className={styles.addTaskButtonContainer}>
<AddNewTaskBtn handleBtn={handleAddTaskBtn} />
</div>
<ColumnCard {...CardProps} />
<ColumnCard {...CardProps2} />
<ColumnCard {...CardProps} />
<ColumnCard {...CardProps} />
<ColumnCard {...CardProps2} />
<ColumnCard {...CardProps} />
<ColumnCard {...CardProps} />
<ColumnCard {...CardProps2} />
<ColumnCard {...CardProps} />
<CardList
cardList={cardList}
hasNext={hasNext}
setElement={setElement}
columnData={columnData}
/>
{settingModalOpen ? (
<EditColumnManagement
isOpen={settingModalOpen}
setIsOpen={setSettingModalOpen}
dashboardId={Number(dashboardId)}
columnId={columnId}
columnTitle={title}
/>
) : null}
{addCardModalOpen ? (
<NewTodoModal
isOpen={addCardModalOpen}
setIsOpen={setAddCardModalOpen}
columnId={columnId}
dashboardId={Number(dashboardId)}
userId={userId}
/>
) : null}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ContainerProps {
children: ReactNode;
}

// 반응형으로 컬럼을 배치하기 위한 컴포넌트
function ColumnContainer({ children }: ContainerProps) {
const { width } = useWindowSize();

Expand Down
Loading

0 comments on commit 1ccd6a2

Please sign in to comment.