-
Notifications
You must be signed in to change notification settings - Fork 2
✨ Feat: 대시보드 상세 페이지 - 드래그 앤 드롭 #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f320492
Merge branch 'develop' into feature/dashboard_id
2c24b07
Merge branch 'develop' into feature/dashboard_id
be838c8
✨ Feat: useCardMutation 작성(낙관적 UI - API함수)
e5ff15f
✨ feat: 드래그앤드롭 이벤트 적용
8cf9929
📝 docs: 상세한 주석은 제거함
37d1cf1
Merge branch 'develop' into feature/dashboard_id-dnd
ea0799e
🐛 fix: 🐰 타입과 컴포넌트 식별자가 겹침 -> Card 타입 임포트 시 별칭 부여(CardType)
2dbfd2b
🐛 fix: 🐰 같은 컬럼으로 드롭 시 뮤테이션 호출 피하기(바로 return)
53057b9
🐛 fix: currentCard가 null상태로 반영되어 setQueryData가 실행되지 못하는 문제
e35da1d
🫧 modify: 불필요한 코드 삭제
c405b46
🐛 🐰 fix: 드래그 시작 시점의 currentCard.columnId 도 함께 cancelQueries / 방어용 리턴…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import axiosClient from '@/app/api/axiosClient' | ||
|
|
||
| // 카드 이동 - 해당 카드의 컬럼ID를 변경하는 방식(PUT) | ||
| export async function updateCardColumn( | ||
| cardId: number, | ||
| columnId: number, | ||
| ): Promise<{ success: boolean }> { | ||
| const res = await axiosClient.put<{ success: boolean }>(`/cards/${cardId}`, { | ||
| columnId: columnId, | ||
| }) | ||
| return res.data | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import { useMutation, useQueryClient } from '@tanstack/react-query' | ||
|
|
||
| import type { Card, CardResponse } from '@/app/api/useCards' | ||
|
|
||
| import { useDragStore } from '../store/useDragStore' | ||
| import { updateCardColumn } from './updateCardColumn' | ||
|
|
||
| export const useCardMutation = () => { | ||
| const queryClient = useQueryClient() | ||
| const { clearDraggingCard } = useDragStore() | ||
|
|
||
| return useMutation({ | ||
| // 1. 서버 API 호출 | ||
| // cardId: 드래그한 카드 아이디, columnId: dragOver된 타겟 컬럼 아이디 | ||
| mutationFn: ({ cardId, columnId }: { cardId: number; columnId: number }) => | ||
| updateCardColumn(cardId, columnId), | ||
|
|
||
| // 2. 낙관적 UI 처리 (서버 요청 전에 실행됨) | ||
| onMutate: async ({ cardId, columnId }) => { | ||
| const currentCard = useDragStore.getState().draggingCard | ||
|
|
||
| await Promise.all([ | ||
| queryClient.cancelQueries({ queryKey: ['columnId', columnId] }), | ||
| queryClient.cancelQueries({ | ||
| queryKey: ['columnId', currentCard?.columnId], | ||
| }), | ||
| ]) | ||
|
|
||
| // 업데이트 이전 데이터 챙겨뒀다가 롤백할때 사용 | ||
| const previousData = queryClient.getQueryData<CardResponse>([ | ||
| 'columnId', | ||
| columnId, | ||
| ]) | ||
|
|
||
| // Guard return | ||
| if ( | ||
| !currentCard || | ||
| currentCard.cardId !== cardId || | ||
| currentCard.columnId === columnId | ||
| ) { | ||
| console.log('no dragging card || is not a dragging card || same column') | ||
| clearDraggingCard() | ||
| return | ||
| } | ||
|
|
||
| let extractedCard: Card | undefined // B. 에서 사용할 예정(추가할 카드 데이터는 Card여야 해서) | ||
| // A. 이전 컬럼에서 카드 제거 & 카드 추출 | ||
| // setQueryData의 콜백함수의 리턴값이 쿼리키 캐시에 저장됨(캐시 업데이트) | ||
| queryClient.setQueryData<CardResponse>( | ||
| ['columnId', currentCard.columnId], | ||
| (oldData) => { | ||
| if (!oldData) return | ||
|
|
||
| const filtered = oldData.cards.filter((card) => { | ||
| if (card.id === cardId) extractedCard = card | ||
| return card.id !== cardId | ||
| }) | ||
|
|
||
| return { ...oldData, cards: filtered } | ||
| }, | ||
| ) | ||
| // B. 새 컬럼에 카드 추가 | ||
| if (extractedCard) { | ||
| queryClient.setQueryData<CardResponse>( | ||
| ['columnId', columnId], | ||
| (oldData) => { | ||
| if (!oldData) return | ||
|
|
||
| const movedCard = { ...extractedCard!, columnId } | ||
| return { | ||
| ...oldData, | ||
| cards: [...oldData.cards, movedCard], | ||
| } | ||
| }, | ||
| ) | ||
| } else { | ||
| console.log('카드가 제거 중에 undefined가 됨') | ||
| } | ||
|
|
||
| clearDraggingCard() | ||
| return { previousData } | ||
| }, | ||
|
|
||
| // 3. 에러 발생 시 롤백 | ||
| onError: (error, variables, context) => { | ||
| if (context?.previousData) { | ||
| queryClient.setQueryData<CardResponse>( | ||
| ['columnId', variables.columnId], | ||
| context.previousData, | ||
| ) | ||
| } | ||
| console.error('카드 이동 실패:', error) | ||
| }, | ||
|
|
||
| // 4. 성공 시 서버 기준으로 다시 데이터 불러오도록 유도함. | ||
| onSuccess: (_data, variables) => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: ['columnId', variables.columnId], | ||
| }) | ||
| }, | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { create } from 'zustand' | ||
| interface draggingCard { | ||
| cardId: number | ||
| columnId: number | ||
| } | ||
| interface DragStore { | ||
| draggingCard: draggingCard | null | ||
| setDraggingCard: (data: { cardId: number; columnId: number }) => void | ||
| clearDraggingCard: () => void | ||
| } | ||
| export const useDragStore = create<DragStore>((set) => ({ | ||
| draggingCard: null, | ||
| setDraggingCard: (data) => set({ draggingCard: data }), | ||
| clearDraggingCard: () => set({ draggingCard: null }), | ||
| })) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
롤백 데이터가 대상 컬럼만 저장되어 불완전합니다
previousData에서 원본·대상 두 컬럼을 모두 보관하지 않으면서버 오류 시 원본 컬럼에 카드가 영영 사라질 수 있습니다.
onError에서 두 컬럼을 모두 복구하도록 구조를 확장하세요.Also applies to: 78-83
🤖 Prompt for AI Agents