Skip to content

Conversation

@dkslel1225
Copy link
Contributor

@dkslel1225 dkslel1225 commented Jun 12, 2025

📌 변경 사항 개요

  • 드래그 앤 드롭 구현
  • 낙관적 UI 적용

추가 수정사항

See commit 2a4f1c3
### ✨ What’s changed

  • 캐시 업데이트 시 draggingCard가 null로 들어가는 문제 수정
  • clearDraggingCard 호출 시점을 setQueryData 이후로 이동
  • 카드 이동 로직을 "컬럼 제거 + 추가" 방식으로 변경

🐛 Background

  • 드롭 이벤트 내에서 clearDraggingCard가 먼저 실행되어, 캐시에 null 상태가 반영되던 이슈 발생
  • 관련 커밋: 2a4f1c3

✅ Checklist

  • 캐시 정상 반영 확인
  • 카드 이동 시 UI 이상 없음

✨ 요약

  • 카드를 특정 컬럼 위에 드롭하면, 카드 데이터에 들어있는 coumnId를 새로운 컬럼아이디로 수정합니다(PUT)
  • 해당 API호출은 낙관적UI를 위해, 캐시된 내용을 먼저 업데이트한 후에 호출하게 됩니다.(useCardMutation 참고)
  • useCardMutation은 카드가 '드롭'되었을때, 이벤트 핸들러 함수 내에서 호출합니다.

📝 상세 내용

🎄 useCardMutation - 수정되었음, 아래 내용은 무효함

//useCardMutation.ts
import { useMutation, useQueryClient } from '@tanstack/react-query'

import type { CardResponse } from '@/app/api/useCards'

import { updateCardColumn } from './updateCardColumn'

export const useCardMutation = () => {
  const queryClient = useQueryClient()

  return useMutation({
    // 1. 서버 API 호출
    // updateCardColumn 카드를 put해서 타겟 컬럼으로 카드 데이터를 수정함
    mutationFn: ({ cardId, columnId }: { cardId: number; columnId: number }) =>
      updateCardColumn(cardId, columnId),

    // 2. 낙관적 UI 처리 (서버 요청 전에 실행됨)
    // 타겟 컬럼에 드래그한 카드 추가하기
    onMutate: async ({ cardId, columnId }) => {
      // cancelQueries:  낙관적 업데이트(onMutate) 전에 서버 쿼리(fetch)가 아직 진행 중이라면 그것을 중단(cancel)하기 위함
      // await: cancel이 완료된 뒤에 setQueryData가 실행되도록 하기 위함
      await queryClient.cancelQueries({ queryKey: ['columnId'] })

      // 업데이트 이전 데이터 챙겨뒀다가 롤백할때 사용
      // ['columnId', columnId]이 키를 기반으로 컬럼에 해당하는 카드 목록을 가져옴
      const previousData = queryClient.getQueryData<CardResponse>([
        'columnId',
        columnId,
      ])

      // 낙관적 업데이트(캐시 업데이트)
      // setQueryData 함수는 'columnId'라는 쿼리 키에 저장된 기존 데이터를 내부에서 꺼내서
      // 업데이트 함수의 인자 oldCards에 보내줌.
      // * CardResponse형을 저장해둬서, 원하는, 카드형 배열을 꺼내려면 cards까지 접근해야 함
      queryClient.setQueryData<CardResponse>(
        ['columnId', columnId],
        (oldCards) => {
          if (!oldCards) return undefined
          return {
            ...oldCards,
            cards: oldCards.cards.map((card) =>
              card.id === cardId ? { ...card, columnId: columnId } : card,
            ),
          }
        },
      )

      return { previousData }
    },

    // 3. 에러 발생 시 롤백
    onError: (error, variables, context) => {
      if (context?.previousData) {
        queryClient.setQueryData<CardResponse>(
          ['columnId', variables.columnId],
          context.previousData,
        )
      }
      console.error('카드 이동 실패:', error)
    },

    // 4. 성공 시 서버 기준으로 다시 데이터 불러오도록 유도함.
    onSuccess: () => {
      // invalidateQueries: 쿼리를 stale 상태로 바꿔(무효화) --> 관련 컴포넌트가 자동으로 refetch(재요청)
      queryClient.invalidateQueries({
        queryKey: ['columnId'],
      })
    },
  })
}

🎄 컴포넌트에 추가한 드래그 이벤트

카드 컴포넌트: dragStart 이벤트: draggingCard에 카드 정보 저장함(아이디,컬럼아이디)

컬럼 컴포넌트:

  • onDragOver: 드래그한 카드가 컬럼에 over되었을때를 감지하기 위해, setDraggingover(true)
  • onDragLeave: 드래그한 카드가 다른 컬럼쪽으로 떠났으면, setDraggingover(false)
  • onDrop:
      1. 드래그가 끝났을때, setDraggingover(false)
      1. 드래그한 카드의 정보를 가져옴, useDragStore.getState
      1. 해당 정보를 useCardMutation 훅에 전달해서 실행함(낙관적 UI)
      1. 드래그한 카드의 정보를 리셋함. clearDraggingCard
  • b,d 에서 사용된 함수는 draggingCard 전역상태를 관리하는, useDragStore에서 가져옴(zustand 사용)

  • setDraggingover에서 설정된 값을 체크해서, 카드가 컬럼에 over 되었을때 해당 컬럼의 border컬러가 바뀜

          className={cn(
            'BG-gray Border-column flex w-354 shrink-0 flex-col gap-16 p-20',
            {
              '!border-blue-500': isDraggingover,
            },
          )}

🔗 관련 이슈

🖼️ 스크린샷

2025-06-13.3.51.10.mov

✅ 체크리스트

  • 브랜치 네이밍 컨벤션을 준수했습니다
  • 커밋 컨벤션을 준수했습니다
  • 코드가 프로젝트의 스타일 가이드라인을 준수합니다

💡 참고 사항

  • 모바일 환경에서는 HTML5 Drag and Drop API (dragstart, drag, drop 등) 가 기본적으로 터치 이벤트 (touchstart, touchmove, touchend) 를 지원하지 않기 때문에, 터치이벤트로 한차례 더 작업해야 합니다.
  • 카드를 컬럼에 dragOver했을때 해당 컬럼의 배경색이 어두워진다거나, 드래그중인 카드는 원래 위치에서 반투명해진다거나 ux개선도 고려중

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능

    • 카드의 드래그 앤 드롭 기능이 추가되어, 카드를 다른 컬럼으로 이동할 수 있습니다.
    • 카드 이동 시 실시간으로 UI와 서버 데이터가 동기화됩니다.
    • 드래그 상태를 관리하는 전역 상태 관리 기능이 도입되었습니다.
  • 스타일

    • 특정 영역에서 텍스트 선택이 불가능하도록 개선되었습니다.
  • 버그 수정

    • 카드 내 이미지가 독립적으로 드래그되지 않도록 처리되었습니다.

@dkslel1225 dkslel1225 self-assigned this Jun 13, 2025
@dkslel1225 dkslel1225 added the ✨Feat 기능 개발 label Jun 13, 2025
@dkslel1225 dkslel1225 added this to the 2차 구현 기간 milestone Jun 13, 2025
@dkslel1225 dkslel1225 linked an issue Jun 13, 2025 that may be closed by this pull request
1 task
@dkslel1225 dkslel1225 changed the base branch from feature/dashboard_id to develop June 13, 2025 02:04
@CoPlay-FE CoPlay-FE deleted a comment from coderabbitai bot Jun 13, 2025
@CoPlay-FE CoPlay-FE deleted a comment from coderabbitai bot Jun 13, 2025
@dkslel1225
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jun 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jun 13, 2025

"""

Walkthrough

이 변경사항은 대시보드 카드와 컬럼 컴포넌트에 드래그 앤 드롭 기능을 추가합니다. 카드 컴포넌트가 드래그 시작 이벤트를 처리하고, 컬럼 컴포넌트는 드래그 오버 및 드롭 이벤트를 처리하며, 전역 드래그 상태 스토어와 카드 컬럼 업데이트 API 및 뮤테이션 훅이 도입되었습니다. 일부 스타일도 업데이트되었습니다.

Changes

파일 경로 (일부 생략) 변경 내용 요약
src/app/dashboard/[id]/Card/Card.tsx card prop 타입명 변경, columnId prop 추가, 드래그 시작 이벤트 처리 및 이미지 드래그 방지 설정
src/app/dashboard/[id]/Column/Column.tsx 드래그 오버, 드래그 리브, 드롭 이벤트 핸들러 추가, isDraggingover 상태 관리, CardcolumnId prop 전달
src/app/dashboard/[id]/api/updateCardColumn.ts 카드 컬럼 변경을 위한 PUT API 함수 updateCardColumn 추가
src/app/dashboard/[id]/api/useCardMutation.ts 카드 컬럼 변경을 위한 React Query 뮤테이션 훅 useCardMutation 추가
src/app/dashboard/[id]/store/useDragStore.ts 드래그 상태 관리용 Zustand 스토어 useDragStore 추가
src/app/dashboard/[id]/page.tsx 특정 div에 select-none CSS 클래스 추가하여 텍스트 선택 방지

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Card
    participant useDragStore
    participant Column
    participant useCardMutation
    participant updateCardColumn(API)

    User->>Card: 카드 드래그 시작
    Card->>useDragStore: setDraggingCard({cardId, columnId})

    User->>Column: 카드 드롭
    Column->>useDragStore: draggingCard 조회
    Column->>useCardMutation: mutate({cardId, targetColumnId})
    useCardMutation->>updateCardColumn: PUT /cards/{cardId} (columnId)
    updateCardColumn-->>useCardMutation: {success: boolean}
    useCardMutation->>Column: 쿼리 무효화 및 상태 업데이트
    Column->>useDragStore: clearDraggingCard()
Loading

Possibly related PRs

  • CoPlay-FE/coplan#37: Card와 Column 컴포넌트의 초기 구현 PR로, 이번 PR의 드래그 앤 드롭 기능이 이 컴포넌트들을 기반으로 확장되었습니다.

Poem

토끼는 오늘도 코드를 만지작
카드가 쏙쏙 움직이는 마법을 짜네
드래그하면 쏙, 컬럼에 딱!
스토어에 살짝, API에 콕!
대시보드 위를 뛰노는 카드들
토끼의 발끝에서 춤을 추네 🐇✨
"""

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-13T16_53_24_001Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e35da1d and c405b46.

📒 Files selected for processing (1)
  • src/app/dashboard/[id]/api/useCardMutation.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/dashboard/[id]/api/useCardMutation.ts
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/app/dashboard/[id]/api/updateCardColumn.ts (1)

4-12: API 호출 예외 처리를 명시적으로 해주세요

현재 오류 발생 시 Axios 예외가 상위로 전파되어 호출부에서만 처리해야 합니다.
try / catch 로 감싸서 로그를 남기거나 Error 객체를 래핑해주면 문제 디버깅이 수월합니다.

 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
+  try {
+    const res = await axiosClient.put<{ success: boolean }>(
+      `/cards/${cardId}`,
+      { columnId },
+    )
+    return res.data
+  } catch (error) {
+    // TODO: sentry 등 중앙화 로깅 처리
+    console.error('updateCardColumn 실패', error)
+    throw error
+  }
 }
src/app/dashboard/[id]/Card/Card.tsx (1)

18-22: 드래그 종료 시 상태 초기화 누락

onDragEnd 또는 onDragLeave 이벤트에서 clearDraggingCard() 를 호출하지 않으면
드래그가 취소됐을 때 전역 상태가 남아 의도치 않은 드롭 동작을 유발할 수 있습니다.

   <div
     draggable="true"
     onDragStart={() => setDraggingCard({ cardId: id, columnId })}
+    onDragEnd={() => clearDraggingCard()}
     className="BG-white Border-section relative w-314 rounded-6 border-solid px-20 py-16"
   >
src/app/dashboard/[id]/Column/Column.tsx (2)

23-30: 드래그 오버 상태 변경 빈도 최적화 제안

onDragOver 이벤트는 매우 자주 발생합니다. !isDraggingover 체크로 setState 호출을 줄이고 있지만, isDraggingovertrue 인 동안 계속 onDragOver 가 불필요하게 실행됩니다. throttle 혹은 pointerenter/pointerleave 로 대체하면 리렌더 횟수를 크게 줄일 수 있습니다.


40-44: 접근성: 드래그 오버 시 시각적 피드백 외 ARIA 속성 추가 고려

파란 테두리만으로는 스크린리더 사용자가 상태 변화를 인지하기 어렵습니다.
aria-dropeffect="move" 또는 aria-describedby 등을 활용해 접근성을 개선해 주세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc76955 and 37d1cf1.

📒 Files selected for processing (6)
  • src/app/dashboard/[id]/Card/Card.tsx (2 hunks)
  • src/app/dashboard/[id]/Column/Column.tsx (2 hunks)
  • src/app/dashboard/[id]/api/updateCardColumn.ts (1 hunks)
  • src/app/dashboard/[id]/api/useCardMutation.ts (1 hunks)
  • src/app/dashboard/[id]/page.tsx (1 hunks)
  • src/app/dashboard/[id]/store/useDragStore.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/app/dashboard/[id]/api/useCardMutation.ts (2)
src/app/dashboard/[id]/api/updateCardColumn.ts (1)
  • updateCardColumn (4-12)
src/app/api/useCards.ts (1)
  • CardResponse (25-29)
src/app/dashboard/[id]/Card/Card.tsx (2)
src/app/api/useCards.ts (1)
  • Card (11-24)
src/app/dashboard/[id]/store/useDragStore.ts (1)
  • useDragStore (8-12)
🪛 Biome (1.9.4)
src/app/dashboard/[id]/Column/Column.tsx

[error] 11-11: Shouldn't redeclare 'Column'. Consider to delete it or rename it.

'Column' is defined here:

(lint/suspicious/noRedeclare)

src/app/dashboard/[id]/Card/Card.tsx

[error] 8-8: Shouldn't redeclare 'Card'. Consider to delete it or rename it.

'Card' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (2)
src/app/dashboard/[id]/store/useDragStore.ts (1)

8-12: Zustand 스토어 설계 적절

간단·명확하게 drag 상태를 관리하고 있으며 불필요한 복잡도가 없습니다. 현 단계에서 추가 코멘트 없습니다.

src/app/dashboard/[id]/page.tsx (1)

17-17: select-none 추가 👍

드래그 중 텍스트 선택 방지 목적이 분명하며 사이드-이펙트도 없으므로 그대로 유지해도 좋습니다.

Comment on lines 16 to 40
onMutate: async ({ cardId, columnId }) => {
await queryClient.cancelQueries({ queryKey: ['columnId'] })

// 업데이트 이전 데이터 챙겨뒀다가 롤백할때 사용
const previousData = queryClient.getQueryData<CardResponse>([
'columnId',
columnId,
])

// 낙관적 업데이트(캐시 업데이트)
queryClient.setQueryData<CardResponse>(
['columnId', columnId],
(oldCards) => {
if (!oldCards) return undefined
return {
...oldCards,
cards: oldCards.cards.map((card) =>
card.id === cardId ? { ...card, columnId: columnId } : card,
),
}
},
)

return { previousData }
},
Copy link

@coderabbitai coderabbitai bot Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

낙관적 UI 로직이 실질적으로 카드 이동을 반영하지 못합니다

  1. 대상 컬럼 캐시에 카드가 없을 경우 map 연산으로는 아무 변화가 없어 UI가 그대로입니다.
  2. 원본 컬럼에서 카드를 제거하지 않아 드래그 이후 두 컬럼에 동시에 표시될 가능성이 있습니다.
  3. cancelQueries / invalidateQueries 키가 ['columnId'] 로 뭉뚱그려져 있어 모든 컬럼을 불필요하게 무효화합니다.

최소한 다음과 같이 수정해야 합니다.

- await queryClient.cancelQueries({ queryKey: ['columnId'] })
+ await queryClient.cancelQueries({ queryKey: ['columnId', columnId] })

 ...

- const previousData = queryClient.getQueryData<CardResponse>([
-   'columnId',
-   columnId,
- ])
+ const previousTarget = queryClient.getQueryData<CardResponse>([
+   'columnId',
+   columnId,
+ ])
+ // 드래그 전 컬럼도 기억해야 롤백 가능
+ const previousSource = queryClient
+   .getQueryData<CardResponse>(['columnId', sourceColumnId])

 ...

- queryClient.setQueryData<CardResponse>(
-   ['columnId', columnId],
-   (oldCards) => {
-     if (!oldCards) return undefined
-     return {
-       ...oldCards,
-       cards: oldCards.cards.map((card) =>
-         card.id === cardId ? { ...card, columnId: columnId } : card,
-       ),
-     }
-   },
- )
+ // 1) 대상 컬럼에 카드 추가
+ queryClient.setQueryData<CardResponse>(['columnId', columnId], (old) => {
+   if (!old) return old
+   const movedCard =
+     old.cards.find((c) => c.id === cardId) ??
+     ({ ...variables.card, columnId } as Card) // fallback
+   return { ...old, cards: [...old.cards, movedCard] }
+ })
+
+ // 2) 원본 컬럼에서 카드 제거
+ queryClient.setQueryData<CardResponse>(['columnId', sourceColumnId], (old) => {
+   if (!old) return old
+   return { ...old, cards: old.cards.filter((c) => c.id !== cardId) }
+ })

 return { previousTarget, previousSource }

타입·변수(sourceColumnId, variables.card)는 실제 구현에 맞게 조정하세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/dashboard/[id]/api/useCardMutation.ts lines 16 to 40, the optimistic
UI update does not properly reflect card movement because it only updates the
target column cache without removing the card from the source column, and it
uses a generic query key that invalidates all columns unnecessarily. Fix this by
updating both the source and target column caches: remove the card from the
source column's cache and add it to the target column's cache. Also, replace the
generic query key ['columnId'] in cancelQueries and invalidateQueries with
specific keys for the affected columns to avoid unnecessary cache invalidation.

Copy link
Contributor Author

@dkslel1225 dkslel1225 Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카드의 컬럼 id를 바꾸는 방식으로 작동하기 때문에, 원래 컬럼에서 카드를 제거할 필요는 없을것 같지 않아?
1,3은 반영해야겠다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkslel1225 네, 이해했습니다. 말씀하신 대로 서버 응답 이후에 다시 fetch 해 정확한 상태를 맞춘다면 원본 컬럼 캐시를 직접 건드리지 않아도 동작에는 문제가 없습니다. 다만 몇 가지만 더 확인해 보시면 좋을 것 같아요.

  1. UX 이슈
    • 네트워크 지연이 있는 환경에서는 드래그 직후 순간적으로 두 컬럼에 카드가 동시 표시될 수 있습니다. 사용자가 눈치채기 힘들 정도라면 괜찮지만, 실사용-latency(모바일 LTE 등)에서 한 번 체크해 보시면 좋겠습니다.

  2. 롤백 안정성
    onError 에서 target 컬럼만 롤백하면, 낙관적으로 추가됐던 카드가 그대로 남을 수 있습니다. 최소한 target 컬럼의 previousData만 아니라 source 컬럼의 previousData도 같이 저장해 두면 더 안전합니다.

  3. 구현 팁 (1, 3 반영용 예시)

// 1️⃣ 대상 컬럼만 선택적으로 cancel / invalidate
await queryClient.cancelQueries({ queryKey: ['columnId', columnId] });
// ...
queryClient.invalidateQueries({ queryKey: ['columnId', columnId] });

// 2️⃣ 대상 컬럼 캐시에 카드가 없을 때 push
queryClient.setQueryData<CardResponse>(['columnId', columnId], old => {
  if (!old) return undefined;
  const exists = old.cards.some(c => c.id === cardId);
  return exists ? old : { ...old, cards: [...old.cards, { ...variables.card, columnId }] };
});

중복 표시나 롤백 시나리오가 실제로 문제되지 않는다면 지금 계획대로 진행하셔도 됩니다. 추가 논의가 필요하면 언제든 말씀 주세요!

Copy link

@Insung-Jo Insung-Jo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

드래그 앤 드롭 구현 수고 많으셨습니다! 낙관적 UI에 대한 내용이 코드로 담겨 있어서 공부가 되었습니다 👍 👍

Copy link
Contributor

@yuj2n yuj2n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지윤님 드래그 앤 드롭 구현 수고 많으셨습니다!!
로직이 많아서 어렵네요 ㅎㅎ😅
그래도 설명으로 적어주셔서 어떤 식으로 동작하는 지 이해했습니다!!

Copy link

@LeeCh0129 LeeCh0129 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

낙관적 UI까지 적용하셨네요!👍, 상세내용과 주석에 다 설명을 적어주셔서 이해하는데 도움이 되었습니다
고생 많으셨습니다

@CoPlay-FE CoPlay-FE deleted a comment from coderabbitai bot Jun 13, 2025
@CoPlay-FE CoPlay-FE deleted a comment from coderabbitai bot Jun 13, 2025
@CoPlay-FE CoPlay-FE deleted a comment from coderabbitai bot Jun 13, 2025
jyn added 2 commits June 14, 2025 01:21
clearDraggingCard가 뮤테이션보다 먼저 실행되어 setQueryData 시
currentCard = draggingCard가 null로 출력됨.
clearDraggingCard 호출을 캐시 업데이트 이후로 이동하여 정상 반영되도록 수정.

또한, 카드 이동은 컬럼에서 제거/추가하는 방식으로 수정함.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/app/dashboard/[id]/api/useCardMutation.ts (2)

38-52: filter 내부 부수효과는 가독성과 타입 안정성을 떨어뜨립니다

카드 추출(extractedCard) 과 제거 로직을 분리하면 의도가 명확해집니다.

-const filtered = oldData.cards.filter((card) => {
-  if (card.id === cardId) extractedCard = card
-  return card.id !== cardId
-})
+const extracted = oldData.cards.find((c) => c.id === cardId)
+const filtered = oldData.cards.filter((c) => c.id !== cardId)
+extractedCard = extracted

61-65: 컬럼 카드 수(totalCount) 동기화 누락

cards 배열만 수정하면 UI 상 카드 개수 뱃지가 즉시 맞지 않을 수 있습니다.
totalCount 를 함께 증감시켜 낙관적 UI의 완성도를 높이세요.

-return {
-  ...oldData,
-  cards: [...oldData.cards, movedCard],
-}
+return {
+  ...oldData,
+  totalCount: oldData.totalCount + 1,
+  cards: [...oldData.cards, movedCard],
+}
src/app/dashboard/[id]/Column/Column.tsx (1)

1-1: 사용하지 않는 useQueryClient import 제거

미사용 import 는 번들 크기와 린트 경고를 불필요하게 증가시킵니다.

-import { useQueryClient } from '@tanstack/react-query'
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37d1cf1 and e35da1d.

📒 Files selected for processing (4)
  • src/app/dashboard/[id]/Card/Card.tsx (2 hunks)
  • src/app/dashboard/[id]/Column/Column.tsx (2 hunks)
  • src/app/dashboard/[id]/api/useCardMutation.ts (1 hunks)
  • src/app/dashboard/[id]/store/useDragStore.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/dashboard/[id]/store/useDragStore.ts
  • src/app/dashboard/[id]/Card/Card.tsx

Comment on lines +23 to +27
const previousData = queryClient.getQueryData<CardResponse>([
'columnId',
columnId,
])

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

롤백 데이터가 대상 컬럼만 저장되어 불완전합니다

previousData 에서 원본·대상 두 컬럼을 모두 보관하지 않으면
서버 오류 시 원본 컬럼에 카드가 영영 사라질 수 있습니다.
onError 에서 두 컬럼을 모두 복구하도록 구조를 확장하세요.

Also applies to: 78-83

🤖 Prompt for AI Agents
In src/app/dashboard/[id]/api/useCardMutation.ts around lines 23 to 27 and also
lines 78 to 83, the rollback data only saves the target column, which is
incomplete. Modify the code to retrieve and store both the original and target
columns' data in previousData. Then update the onError handler to restore both
columns to ensure no data loss occurs if the server operation fails.

@dkslel1225 dkslel1225 merged commit 74597e5 into develop Jun 14, 2025
2 checks passed
@dkslel1225 dkslel1225 added the 🐛Fix 버그 수정 label Jun 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 기능 개발 🐛Fix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feat: 대시보드 상세 페이지 - 드래그 앤 드롭

5 participants