Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/app/features/dashboard_Id/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export default function Card({
<CreateCardModal>
<ModifyCardForm
onClose={() => setOpenModifyCard(false)}
// columnId={column.id}
currentColumn={currentColumn}
card={card}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default function CreateCardForm({
width={26}
height={24}
className={cn(
'pointer-events-none absolute right-0 top-1/2 -translate-y-1/2 transition-transform duration-300',
'pointer-events-none absolute right-0 top-1/2 -translate-y-1/2 transition-transform duration-300 mobile:right-8',
isOpen && 'rotate-180',
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function CreateCardModal({ children }: ModalProps) {

return createPortal(
<div className="fixed inset-0 flex items-center justify-center bg-black/40">
<div className="BG-white h-970 w-584 overflow-y-scroll rounded-16 p-32 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)]">
<div className="BG-white h-970 w-584 overflow-y-scroll rounded-16 p-32 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)] mobile:h-766 mobile:w-327 mobile:px-16 mobile:py-24">
{children}
</div>
</div>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default function ModifyCardForm({
>
<h2 className="Text-black text-24 font-bold">할 일 수정</h2>

<div className="flex gap-32">
<div className="flex gap-32 mobile:flex-col">
{/* 컬럼 선택 */}
<Controller
name="columnId"
Expand All @@ -144,13 +144,13 @@ export default function ModifyCardForm({
onClick={() => setIsOpenColumn((prev) => !prev)}
value={selectedColumn?.columnTitle ?? ''}
readOnly
className="Input-readOnly w-217"
className="Input-readOnly-217"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

입력 필드 크기 불일치
Input-readOnly-217 인풋 너비(217px)가 래퍼의 w-207(207px)보다 커 오버플로우가 발생할 수 있습니다. 래퍼 또는 인풋 클래스 중 하나를 w-217 또는 w-207으로 일치시키는 패치가 필요합니다.

- <div className="relative w-207">
+ <div className="relative w-217">

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

🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx at line
147, the input field has a class setting its width to 217px, but its wrapper has
a width of 207px, causing overflow issues. To fix this, adjust either the
input's class to match the wrapper's width by changing it to 207px or update the
wrapper's width class to 217px so both widths are consistent and prevent
overflow.

id="columnId"
type="text"
placeholder={currentColumn.columnTitle}
/>
{/* 인풋에 보이는 선택된 컬럼 & 오른쪽 화살표 */}
<div className="absolute left-16 top-1/2 -translate-y-1/2">
<div className="absolute left-16 top-1/2 -translate-y-1/2 mobile:left-11">
<ColumnTitle title={selectedColumn.columnTitle} />
</div>
<Image
Expand Down Expand Up @@ -187,7 +187,7 @@ export default function ModifyCardForm({
onClick={() => setIsOpen((prev) => !prev)}
// value={selectedAssignee?.nickname ?? ''}
readOnly
className="Input-readOnly w-217"
className="Input-readOnly-217"
id="assigneeUserId"
type="text"
/>
Expand Down
18 changes: 8 additions & 10 deletions src/app/features/dashboard_Id/Card/cardModal/CardContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Image from 'next/image'
import { useState } from 'react'
import { useRef, useState } from 'react'

import { Avatar } from '@/app/shared/components/common/Avatar'
import Dropdown from '@/app/shared/components/common/Dropdown/Dropdown'
Expand All @@ -8,8 +8,6 @@ import { useIsMobile } from '@/app/shared/hooks/useIsmobile'
import { useDeleteCardMutation } from '../../api/useDeleteCardMutation'
import { Card } from '../../type/Card.type'
import { Column } from '../../type/Column.type'
import CreateCardModal from '../cardFormModals/CreateCardModal'
import ModifyCardForm from '../cardFormModals/ModifyCardForm'
import ColumnTitle from '../ColumnTitle'
import Tags from '../Tags'
import CommentForm from './CommentForm'
Expand All @@ -26,16 +24,16 @@ export default function CardContent({
card: Card
column: Column
}) {
// const { id, imageUrl, title, tags, dueDate, assignee, description } = card
const [openModifyCard, setOpenModifyCard] = useState(false)
// const { title: columnTitle, id: columnId } = column
// const currentColumn = { columnTitle, columnId }
const isMobile = useIsMobile()
const { mutate: deleteCard, isPending } = useDeleteCardMutation()

const modalScrollRef = useRef<HTMLDivElement>(null)

return (
// <div className="relative">
<div>
<div
className="BG-white relative max-h-764 min-h-764 w-730 overflow-auto overflow-y-scroll rounded-16 px-18 py-30 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)] mobile:w-327 mobile:p-16 tablet:w-678 tablet:px-32 tablet:py-24"
ref={modalScrollRef}
>
<h2 className="Text-black mb-24 text-24 font-bold mobile:mt-40 mobile:text-20">
{card.title}
</h2>
Expand Down Expand Up @@ -166,7 +164,7 @@ export default function CardContent({
columnId={card.columnId}
dashboardId={card.dashboardId}
/>
<Comments cardId={card.id} />
<Comments cardId={card.id} scrollRef={modalScrollRef} />
</div>
)
}
4 changes: 1 addition & 3 deletions src/app/features/dashboard_Id/Card/cardModal/CardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ export default function CardModal({ children }: ModalProps) {

return createPortal(
<div className="fixed inset-0 flex items-center justify-center bg-black/40">
<div className="BG-white relative max-h-764 min-h-764 w-730 overflow-auto overflow-y-scroll rounded-16 px-18 py-30 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)] mobile:w-327 mobile:p-16 tablet:w-678 tablet:px-32 tablet:py-24">
{children}
</div>
<div>{children}</div>
</div>,
modalRoot,
)
Expand Down
51 changes: 36 additions & 15 deletions src/app/features/dashboard_Id/Card/cardModal/Comments.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import { format } from 'date-fns'
import { useState } from 'react'
import { toast } from 'sonner'

import { Avatar } from '@/app/shared/components/common/Avatar'

import useCommentsQuery from '../../api/useCommentsQuery'
import { useInfiniteComments } from '../../api/useInfiniteComments'
import { useInfiniteScroll } from '../../hooks/useInfiniteScroll'
import { Comment as CommentType } from '../../type/Comment.type'
import Comment from './Comment'

export default function Comments({ cardId }: { cardId: number }) {
const { data, isLoading, error } = useCommentsQuery(cardId)
const comments = data?.comments
export default function Comments({
cardId,
scrollRef,
}: {
cardId: number
scrollRef: React.RefObject<HTMLElement>
}) {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
} = useInfiniteComments(cardId)

useInfiniteScroll(fetchNextPage, hasNextPage, isFetchingNextPage, scrollRef)

if (error) {
toast.error(error?.message || '문제가 발생했습니다.')
if (isError) {
toast.error('댓글 불러오기 실패')
}
return (
<div className="flex w-450 flex-col gap-20">
{comments?.map((comment: CommentType) => (
<Comment key={comment.id} comment={comment} />
))}
</div>
<>
<div className="flex w-450 flex-col gap-20">
{data?.pages.map((page) =>
page?.comments.map((comment: CommentType) => (
<Comment key={comment.id} comment={comment} />
)),
)}
</div>
{/* 무한 스크롤 관련 */}
{isFetchingNextPage && (
<p className="text-center text-sm text-gray-400">
댓글을 불러오는 중...
</p>
)}
</>
)
}
45 changes: 36 additions & 9 deletions src/app/features/dashboard_Id/Column/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import Image from 'next/image'
import { useState } from 'react'
import { toast } from 'sonner'

import { cn } from '@/app/shared/lib/cn'

import { useCardMutation } from '../api/useCardMutation'
import useCards from '../api/useCards'
import { useInfiniteCards } from '../api/useInfiniteCards'
import Card from '../Card/Card'
import CreateCardForm from '../Card/cardFormModals/CreateCardForm'
import CreateCardModal from '../Card/cardFormModals/CreateCardModal'
import { useInfiniteScroll } from '../hooks/useInfiniteScroll'
import { useDragStore } from '../store/useDragStore'
import type { Column as ColumnType } from '../type/Column.type'
export default function Column({ column }: { column: ColumnType }) {
const { id, title }: { id: number; title: string } = column
const { data, isLoading, error } = useCards(id)

const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
} = useInfiniteCards(id)

useInfiniteScroll(fetchNextPage, hasNextPage, isFetchingNextPage)

const [isDraggingover, setDraggingover] = useState(false)
const { draggingCard, clearDraggingCard } = useDragStore()
const cardMutation = useCardMutation()
Expand All @@ -21,8 +34,8 @@ export default function Column({ column }: { column: ColumnType }) {
const [openCreateColumn, setOpenCreateColumn] = useState(false) //page.tsx
const [oepnConfigColumn, setConfigColumn] = useState(false)

if (isLoading) return <p>loading...</p>
if (error) return <p>error...{error.message}</p>
if (isLoading) return <p>loading...</p> // 스켈레톤 적용???⭐️
if (isError) return toast.error('할 일 불러오기 실패')
Comment on lines +37 to +38
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로딩과 에러 처리 개선 필요

현재 로딩 상태에서 간단한 텍스트만 표시하고, 에러 상태에서도 컴포넌트가 계속 렌더링됩니다. 개선을 고려해보세요.

- if (isLoading) return <p>loading...</p> // 스켈레톤 적용???⭐️
- if (isError) return toast.error('할 일 불러오기 실패')
+ if (isLoading) {
+   return (
+     <div className="BG-gray Border-column flex w-354 shrink-0 flex-col gap-16 p-20 tablet:w-584">
+       <div className="mb-24 flex items-center justify-between">
+         <div className="flex items-center">
+           <div className="BG-blue mb-7 mr-8 size-8 rounded-25"></div>
+           <h2 className="mb-3 mr-12 h-21 text-18 font-bold">{title}</h2>
+         </div>
+       </div>
+       <p className="text-center text-gray-500">카드를 불러오는 중...</p>
+     </div>
+   )
+ }
+ if (isError) {
+   toast.error('할 일 불러오기 실패')
+   return (
+     <div className="BG-gray Border-column flex w-354 shrink-0 flex-col gap-16 p-20 tablet:w-584">
+       <div className="mb-24 flex items-center justify-between">
+         <div className="flex items-center">
+           <div className="BG-blue mb-7 mr-8 size-8 rounded-25"></div>
+           <h2 className="mb-3 mr-12 h-21 text-18 font-bold">{title}</h2>
+         </div>
+       </div>
+       <p className="text-center text-red-500">카드를 불러올 수 없습니다.</p>
+     </div>
+   )
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isLoading) return <p>loading...</p> // 스켈레톤 적용???⭐️
if (isError) return toast.error('할 일 불러오기 실패')
if (isLoading) {
return (
<div className="BG-gray Border-column flex w-354 shrink-0 flex-col gap-16 p-20 tablet:w-584">
<div className="mb-24 flex items-center justify-between">
<div className="flex items-center">
<div className="BG-blue mb-7 mr-8 size-8 rounded-25"></div>
<h2 className="mb-3 mr-12 h-21 text-18 font-bold">{title}</h2>
</div>
</div>
<p className="text-center text-gray-500">카드를 불러오는 중...</p>
</div>
)
}
if (isError) {
toast.error('할 일 불러오기 실패')
return (
<div className="BG-gray Border-column flex w-354 shrink-0 flex-col gap-16 p-20 tablet:w-584">
<div className="mb-24 flex items-center justify-between">
<div className="flex items-center">
<div className="BG-blue mb-7 mr-8 size-8 rounded-25"></div>
<h2 className="mb-3 mr-12 h-21 text-18 font-bold">{title}</h2>
</div>
</div>
<p className="text-center text-red-500">카드를 불러올 수 없습니다.</p>
</div>
)
}
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Column/Column.tsx around lines 37 to 38, the
current loading state only shows simple text and the error state triggers a
toast but still renders the component. Improve this by replacing the loading
text with a proper skeleton loader component and prevent rendering the main
component when there is an error by returning null or an error message component
instead of continuing to render.


return (
<div
Expand Down Expand Up @@ -63,7 +76,7 @@ export default function Column({ column }: { column: ColumnType }) {
<div className="BG-blue mb-7 mr-8 size-8 rounded-25"></div>
<h2 className="mb-3 mr-12 h-21 text-18 font-bold">{title}</h2>
<span className="Text-gray block size-20 rounded-4 bg-[#EEEEEE] pt-3 text-center text-12 font-medium leading-tight dark:bg-[#2E2E2E]">
{data?.totalCount}
{data?.pages[0]?.totalCount ?? 0}
</span>
</div>
<Image
Expand All @@ -88,10 +101,11 @@ export default function Column({ column }: { column: ColumnType }) {
/>
</div>
</button>
{data?.cards.map((card) => (
<Card key={card.id} card={card} column={column} />
))}

{data?.pages.map((page) =>
page.cards.map((card) => (
<Card key={card.id} card={card} column={column} />
)),
)}
{/* 카드 생성 모달 */}
{openCreateCard && (
<CreateCardModal>
Expand All @@ -101,6 +115,19 @@ export default function Column({ column }: { column: ColumnType }) {
/>
</CreateCardModal>
)}

{/* 무한 스크롤 관련 */}
{isFetchingNextPage && (
<p className="text-center text-sm text-gray-400">
카드를 불러오는 중...
</p>
)}

{!hasNextPage && (
<p className="py-4 text-center text-sm text-gray-300">
모든 카드를 불러왔습니다
</p>
)}
</div>
)
}
22 changes: 17 additions & 5 deletions src/app/features/dashboard_Id/api/fetchCards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ import api from '@/app/shared/lib/testAxios'
// import api from '@/app/shared/lib/axios'
import { CardResponse } from '../type/Card.type'

export async function fetchCards(
columnId: number,
size: number = 10,
): Promise<CardResponse> {
export async function fetchCards({
columnId,
size = 6,
cursorId,
}: {
columnId: number
size?: number
cursorId?: number | null
}): Promise<CardResponse> {
const res = await api.get<CardResponse>(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/cards?size=${size}&columnId=${columnId}`,
`/${process.env.NEXT_PUBLIC_TEAM_ID}/cards`,
{
params: {
columnId,
size,
...(cursorId ? { cursorId } : {}), // 첫 페이지는 cursor 생략
},
},
)
return res.data
}
20 changes: 17 additions & 3 deletions src/app/features/dashboard_Id/api/fetchComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@ import api from '@/app/shared/lib/testAxios'
// import api from '@/app/shared/lib/axios'
import { CommentsResponse } from '../type/Comment.type'

export async function fetchComments(cardId: number): Promise<CommentsResponse> {
export async function fetchComments({
cardId,
size = 5,
cursorId,
}: {
cardId: number
size?: number
cursorId?: number | null
}): Promise<CommentsResponse> {
const res = await api.get<CommentsResponse>(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/comments?size=10&cardId=${cardId}`,
`/${process.env.NEXT_PUBLIC_TEAM_ID}/comments`,
{
params: {
size,
...(cursorId ? { cursorId } : {}), // 첫 페이지는 cursor 생략
cardId,
},
},
)

return res.data
}
12 changes: 0 additions & 12 deletions src/app/features/dashboard_Id/api/useCards.ts

This file was deleted.

11 changes: 0 additions & 11 deletions src/app/features/dashboard_Id/api/useCommentsQuery.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/app/features/dashboard_Id/api/useInfiniteCards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useInfiniteQuery } from '@tanstack/react-query'

import { CardResponse } from '../type/Card.type'
import { fetchCards } from './fetchCards'

export function useInfiniteCards(columnId: number) {
return useInfiniteQuery<CardResponse>({
queryKey: ['columnId', columnId],
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 키 네이밍이 부정확합니다

쿼리 키에 'columnId'라는 문자열이 사용되었지만, 실제로는 cards 데이터를 가져오는 쿼리입니다. 의미가 더 명확한 키 이름을 사용하는 것이 좋겠습니다.

-    queryKey: ['columnId', columnId],
+    queryKey: ['cards', columnId],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryKey: ['columnId', columnId],
queryKey: ['cards', columnId],
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/api/useInfiniteCards.ts at line 8, the query
key uses the string 'columnId' which is misleading since the query fetches cards
data. Rename the query key to a more descriptive name like 'cards' or
'cardsByColumn' to accurately reflect the data being queried.

queryFn: ({ pageParam = null }) =>
fetchCards({ columnId, cursorId: pageParam as number }),
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

타입 캐스팅 안전성을 개선해야 합니다

pageParam as number로 강제 타입 캐스팅을 하고 있지만, initialPageParamnull이므로 첫 번째 호출 시 타입 불일치가 발생할 수 있습니다.

    queryFn: ({ pageParam = null }) =>
-      fetchCards({ columnId, cursorId: pageParam as number }),
+      fetchCards({ columnId, cursorId: pageParam as number | null }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryFn: ({ pageParam = null }) =>
fetchCards({ columnId, cursorId: pageParam as number }),
queryFn: ({ pageParam = null }) =>
fetchCards({ columnId, cursorId: pageParam as number | null }),
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/api/useInfiniteCards.ts around lines 9 to 10,
the code forcibly casts pageParam to number using 'pageParam as number', but
since initialPageParam can be null, this causes a type mismatch on the first
call. To fix this, add a runtime check to handle the case when pageParam is null
before casting, ensuring the fetchCards function receives a valid number or
handles null appropriately without unsafe casting.

getNextPageParam: (lastPage) => {
// 마지막 카드의 id를 다음 cursor로 사용
return lastPage.cards.length === 0 ? undefined : lastPage.cursorId
},
initialPageParam: null,
})
}
17 changes: 17 additions & 0 deletions src/app/features/dashboard_Id/api/useInfiniteComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useInfiniteQuery } from '@tanstack/react-query'

import { CommentsResponse } from '../type/Comment.type'
import { fetchComments } from './fetchComments'

export function useInfiniteComments(cardId: number) {
return useInfiniteQuery<CommentsResponse>({
queryKey: ['comments', cardId],
queryFn: ({ pageParam = null }) =>
fetchComments({ cardId, cursorId: pageParam as number }),
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

타입 캐스팅 안전성을 개선해야 합니다

useInfiniteCards와 동일한 문제입니다. pageParam as number로 강제 타입 캐스팅을 하고 있지만, initialPageParamnull이므로 첫 번째 호출 시 타입 불일치가 발생할 수 있습니다.

    queryFn: ({ pageParam = null }) =>
-      fetchComments({ cardId, cursorId: pageParam as number }),
+      fetchComments({ cardId, cursorId: pageParam as number | null }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryFn: ({ pageParam = null }) =>
fetchComments({ cardId, cursorId: pageParam as number }),
queryFn: ({ pageParam = null }) =>
fetchComments({ cardId, cursorId: pageParam as number | null }),
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/api/useInfiniteComments.ts around lines 9 to
10, the code forcibly casts pageParam to number despite initialPageParam being
null, causing a type mismatch on the first call. To fix this, add a runtime
check to handle the null case properly before casting, ensuring that
fetchComments receives a valid number or handles null safely without forced
casting.

getNextPageParam: (lastPage) => {
// 마지막 카드의 id를 다음 cursor로 사용
return lastPage.comments.length === 0 ? undefined : lastPage.cursorId
},
initialPageParam: null,
})
}
1 change: 0 additions & 1 deletion src/app/features/dashboard_Id/api/usePutCardMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import axios from 'axios'
import { toast } from 'sonner'

import { CardModifyFormData } from '../type/CardFormData.type'
import { postCard } from './postCard'
import { putCard } from './putCard'

// ✅ 카드 수정 모달에서 사용 (CreateCardForm.tsx)
Expand Down
Loading