diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-02 00:45:49 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-02 00:45:49 +0000 |
| commit | 2acf5f8966a40c1c9a97680c8dc263ee3f1ad3d1 (patch) | |
| tree | f406b5c86f563347c7fd088a85fd1a82284dc5ff /components/qna/comment-section.tsx | |
| parent | 6a9ca20deddcdcbe8495cf5a73ec7ea5f53f9b55 (diff) | |
(대표님/최겸) 20250702 변경사항 업데이트
Diffstat (limited to 'components/qna/comment-section.tsx')
| -rw-r--r-- | components/qna/comment-section.tsx | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/components/qna/comment-section.tsx b/components/qna/comment-section.tsx new file mode 100644 index 00000000..2ea358e2 --- /dev/null +++ b/components/qna/comment-section.tsx @@ -0,0 +1,166 @@ +import * as React from "react"; +import { useSession } from "next-auth/react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Badge } from "@/components/ui/badge"; +import { format } from "date-fns"; +import { Comment } from "@/lib/qna/types"; +import { Trash2, Pencil, Check, X } from "lucide-react"; + +interface CommentSectionProps { + answerId: string; + comments: Comment[]; + onAddComment: (content: string) => Promise<void>; + onDeleteComment: (commentId: string) => Promise<void>; + onUpdateComment?: (commentId: string, content: string) => Promise<void>; +} + +export function CommentSection({ answerId, comments, onAddComment, onDeleteComment, onUpdateComment }: CommentSectionProps) { + const { data: session } = useSession(); + const [content, setContent] = React.useState(""); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const [editingId, setEditingId] = React.useState<string | null>(null); + const [editContent, setEditContent] = React.useState(""); + + const handleSubmit = async () => { + if (!content.trim() || !session?.user?.name) return; + setIsSubmitting(true); + try { + await onAddComment(content); + setContent(""); + } finally { + setIsSubmitting(false); + } + }; + + const handleEditStart = (comment: Comment) => { + setEditingId(comment.id); + setEditContent(comment.content); + }; + + const handleEditCancel = () => { + setEditingId(null); + setEditContent(""); + }; + + const handleEditSave = async (commentId: string) => { + if (!editContent.trim() || !onUpdateComment) return; + try { + await onUpdateComment(commentId, editContent); + setEditingId(null); + } catch (error) { + console.error("댓글 수정 실패:", error); + } + }; + + return ( + <div className="space-y-4 mt-4"> + <div className="flex items-center gap-2"> + <h3 className="text-sm font-medium">댓글</h3> + {comments.length > 0 && ( + <Badge variant="secondary" className="text-xs"> + {comments.length} + </Badge> + )} + </div> + + {/* 댓글 목록 */} + <div className="space-y-3"> + {comments.map((comment) => ( + <div key={comment.id} className="flex items-start justify-between text-sm bg-muted/50 rounded-md p-2"> + <div className="flex-1 space-y-1"> + <div className="flex items-center gap-2"> + <span className="font-medium">{comment.author}</span> + <span className="text-xs text-muted-foreground"> + {format(new Date(comment.createdAt), "yyyy.MM.dd HH:mm")} + </span> + </div> + {editingId === comment.id ? ( + <Textarea + value={editContent} + onChange={(e) => setEditContent(e.target.value)} + className="min-h-[60px] text-sm" + maxLength={250} + /> + ) : ( + <p className="text-sm">{comment.content}</p> + )} + </div> + {session?.user?.name === comment.author && ( + <div className="flex gap-2"> + {editingId === comment.id ? ( + <> + <Button + variant="ghost" + size="icon" + className="h-6 w-6 text-green-600 hover:text-green-700" + onClick={() => handleEditSave(comment.id)} + > + <Check className="h-4 w-4" /> + </Button> + <Button + variant="ghost" + size="icon" + className="h-6 w-6 text-muted-foreground" + onClick={handleEditCancel} + > + <X className="h-4 w-4" /> + </Button> + </> + ) : ( + <> + <Button + variant="ghost" + size="icon" + onClick={() => handleEditStart(comment)} + className="h-6 w-6 text-muted-foreground hover:text-blue-500 transition-colors" + > + <Pencil className="h-3.5 w-3.5" /> + </Button> + <Button + variant="ghost" + size="icon" + className="h-6 w-6 text-muted-foreground hover:text-destructive transition-colors" + onClick={() => onDeleteComment(comment.id)} + > + <Trash2 className="h-3.5 w-3.5" /> + </Button> + </> + )} + </div> + )} + </div> + ))} + </div> + + {/* 댓글 입력 */} + {session?.user && ( + <div className="flex gap-2"> + <Textarea + value={content} + onChange={(e) => { + if (e.target.value.length <= 250) { + setContent(e.target.value); + } + }} + placeholder="댓글을 입력하세요 (250자 이내)" + className="min-h-[80px] flex-1" + maxLength={250} + /> + <Button + onClick={handleSubmit} + disabled={isSubmitting || !content.trim()} + className="self-start" + > + {isSubmitting ? "저장 중..." : "등록"} + </Button> + </div> + )} + + {/* 글자 수 표시 */} + <div className="text-xs text-muted-foreground text-right"> + {content.length}/250 + </div> + </div> + ); +}
\ No newline at end of file |
