From 7582e9d7a526d865cb2985edab5ea5e2b22bc954 Mon Sep 17 00:00:00 2001 From: "chltjdfkr517@naver.com" Date: Wed, 12 Feb 2025 18:33:13 +0900 Subject: [PATCH 01/10] =?UTF-8?q?:sparkles:=20feat:=20=ED=95=A0=EC=9D=BC?= =?UTF-8?q?=20=EC=B9=B4=EB=93=9C=20=EB=AA=A8=EB=8B=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/TodoCard.tsx | 45 +++++++++++++++++---------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/components/dashboard/TodoCard.tsx b/src/components/dashboard/TodoCard.tsx index c1f55c6..ff4be96 100644 --- a/src/components/dashboard/TodoCard.tsx +++ b/src/components/dashboard/TodoCard.tsx @@ -1,37 +1,50 @@ +'use client'; + import Image from 'next/image'; import { formatDate } from '@/utils/formatDate'; import { Card } from '@/apis/cards/types'; import Avatar from '../ui/Avatar/Avatar'; import TagChip from '../ui/Chip/TagChip'; import calendar from '@/assets/icons/calendar.svg'; +import { useRef } from 'react'; +import { ModalHandle } from '../ui/Modal/Modal'; +import DetailTodo from './DetailTodo'; interface TodoCardProps { card: Card; } export default function TodoCard({ card }: TodoCardProps) { + const detailTodoModalRef = useRef(null); const formattedDate = formatDate(card.createdAt); return ( -
- {card.imageUrl && {card.description}} -
-
- {card.title} -
- {card.tags.map((tag) => ( - - ))} + <> +
detailTodoModalRef.current?.open()} + > + {card.imageUrl && {card.description}} +
+
+ {card.title} +
+ {card.tags.map((tag) => ( + + ))} +
-
-
-
- 캘린더 - {formattedDate} +
+
+ 캘린더 + {formattedDate} +
+
-
-
+ + + ); } From ef86fbceedec8142d82f5912d887771e73ac5157 Mon Sep 17 00:00:00 2001 From: "chltjdfkr517@naver.com" Date: Thu, 13 Feb 2025 16:12:42 +0900 Subject: [PATCH 02/10] =?UTF-8?q?:sparkles:=20feat:=20=ED=95=A0=20?= =?UTF-8?q?=EC=9D=BC=20=EC=B9=B4=EB=93=9C=20=EC=83=81=EC=84=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/CommentSection.tsx | 116 +++++++++++++++++ src/components/dashboard/DetailTodo.tsx | 133 ++++++++++++++++++++ src/components/dashboard/TodoCard.tsx | 8 +- 3 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 src/components/dashboard/CommentSection.tsx create mode 100644 src/components/dashboard/DetailTodo.tsx diff --git a/src/components/dashboard/CommentSection.tsx b/src/components/dashboard/CommentSection.tsx new file mode 100644 index 0000000..acaa0bd --- /dev/null +++ b/src/components/dashboard/CommentSection.tsx @@ -0,0 +1,116 @@ +import { deleteComment, getComments, postComment, putComment } from '@/apis/comments'; +import { Comment, CommentForm } from '@/apis/comments/types'; +import useAlert from '@/hooks/useAlert'; +import { formatDate } from '@/utils/formatDate'; +import { useEffect, useState } from 'react'; +import Avatar from '../ui/Avatar/Avatar'; +import Button from '../ui/Button/Button'; +import { Textarea } from '../ui/Field'; +import { getErrorMessage } from '@/utils/errorMessage'; + +interface CommentSectionProps { + cardId: number; + columnId: number; + dashboardId: number; +} + +export default function CommentSection({ cardId, columnId, dashboardId }: CommentSectionProps) { + const alert = useAlert(); + const [comments, setComments] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [content, setContent] = useState(''); + + useEffect(() => { + fetchComments(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [cardId]); + + async function fetchComments() { + setIsLoading(true); + try { + const data = await getComments({ cardId, size: 10 }); + setComments(data.comments); + } catch (err) { + const message = getErrorMessage(err); + alert(message); + } finally { + setIsLoading(false); + } + } + + async function handleSubmitComment() { + if (!content.trim()) return; + const formData: CommentForm = { + content, + cardId, + columnId, + dashboardId, + }; + try { + const newComment = await postComment(formData); + setComments((prev) => [newComment, ...prev]); + setContent(''); + } catch (err) { + const message = getErrorMessage(err); + alert(message); + } + } + + async function handleEditComment(commentId: number, currentContent: string) { + const newContent = prompt('댓글 수정하기', currentContent); + if (!newContent) return; + try { + const updated = await putComment(commentId, { content: newContent }); + setComments((prev) => prev.map((c) => (c.id === commentId ? updated : c))); + } catch (err) { + const message = getErrorMessage(err); + alert(message); + } + } + + async function handleDeleteComment(commentId: number) { + if (!confirm('댓글을 삭제하시겠습니까?')) return; + try { + await deleteComment(commentId); + setComments((prev) => prev.filter((c) => c.id !== commentId)); + } catch (err) { + const message = getErrorMessage(err); + alert(message); + } + } + + return ( +
+
+