From 2acf5f8966a40c1c9a97680c8dc263ee3f1ad3d1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 2 Jul 2025 00:45:49 +0000 Subject: (대표님/최겸) 20250702 변경사항 업데이트 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/qna/table/qna-detail.tsx | 455 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 lib/qna/table/qna-detail.tsx (limited to 'lib/qna/table/qna-detail.tsx') diff --git a/lib/qna/table/qna-detail.tsx b/lib/qna/table/qna-detail.tsx new file mode 100644 index 00000000..4f0a891f --- /dev/null +++ b/lib/qna/table/qna-detail.tsx @@ -0,0 +1,455 @@ +"use client"; + +import * as React from "react"; +import { useRouter } from "next/navigation"; +import { useSession } from "next-auth/react"; +import { format } from "date-fns"; + +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { + ArrowLeft, + MessageSquare, + Edit, + Trash2, + Plus, + Clock, + User +} from "lucide-react"; + +import TiptapEditor from "@/components/qna/tiptap-editor"; +import { ImprovedCommentSection } from "./improved-comment-section"; +import { + createAnswer, + deleteQna, + deleteAnswer, + updateAnswer, + createComment, + deleteComment, + updateComment, +} from "@/lib/qna/service"; +import { Question } from "@/lib/qna/types"; + +interface QnaDetailProps { + question: Question; +} + +export default function QnaDetail({ question }: QnaDetailProps) { + const router = useRouter(); + const { data: session } = useSession(); + + // ------------------------------------------------------------------------- + // STATE + // ------------------------------------------------------------------------- + const [answerContent, setAnswerContent] = React.useState(""); + const [loading, setLoading] = React.useState(false); + const [isAnswerDialogOpen, setIsAnswerDialogOpen] = React.useState(false); + const [editingAnswerId, setEditingAnswerId] = React.useState(null); + + // ------------------------------------------------------------------------- + // DERIVED + // ------------------------------------------------------------------------- + const isAuthor = session?.user?.id === question.author; + const hasAnswers = (question.answers ?? []).length > 0; + const userAnswer = question.answers?.find((a) => a.author === session?.user?.id) ?? null; + + // ------------------------------------------------------------------------- + // HANDLERS – NAVIGATION + // ------------------------------------------------------------------------- + const handleGoBack = () => { + router.push("/evcp/qna"); + }; + + // ------------------------------------------------------------------------- + // HANDLERS – QUESTION + // ------------------------------------------------------------------------- + const handleDeleteQuestion = async () => { + try { + await deleteQna(question.id); + router.push("/evcp/qna"); + router.refresh(); + } catch (err) { + console.error("질문 삭제 실패:", err); + } + }; + + // ------------------------------------------------------------------------- + // HANDLERS – ANSWER + // ------------------------------------------------------------------------- + const handleSubmitAnswer = async () => { + if (!answerContent.trim()) return; + setLoading(true); + try { + await createAnswer({ qnaId: question.id, content: answerContent }); + setAnswerContent(""); + setIsAnswerDialogOpen(false); + router.refresh(); + } catch (err) { + console.error("답변 저장 실패:", err); + } finally { + setLoading(false); + } + }; + + const handleUpdateAnswer = async (answerId: number) => { + if (!answerContent.trim()) return; + setLoading(true); + try { + await updateAnswer(answerId, answerContent); + setAnswerContent(""); + setEditingAnswerId(null); + setIsAnswerDialogOpen(false); + router.refresh(); + } catch (err) { + console.error("답변 수정 실패:", err); + } finally { + setLoading(false); + } + }; + + const handleDeleteAnswer = async (id: number) => { + try { + await deleteAnswer(id); + router.refresh(); + } catch (err) { + console.error("답변 삭제 실패:", err); + } + }; + + const startEditingAnswer = (answer: any) => { + setAnswerContent(answer.content); + setEditingAnswerId(answer.id); + setIsAnswerDialogOpen(true); + }; + + const resetAnswerDialog = () => { + setAnswerContent(""); + setEditingAnswerId(null); + setIsAnswerDialogOpen(false); + }; + + return ( +
+ {/* 헤더 */} +
+
+
+
+ + {/*
*/} + {/* + {getCategoryLabel(question.category as any, 'ko')} + */} +
+ + {isAuthor && !hasAnswers && ( +
+ + + + + + + + 질문 삭제 + + 정말로 이 질문을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + + + + 취소 + + 삭제 + + + + +
+ )} +
+
+
+ + {/* 메인 컨텐츠 */} +
+
+ {/* 질문 영역 */} + + +
+ + {question.title} + +
+
+ + + + + + + + {question.authorName} + +
+
+ + +
+ {hasAnswers && ( +
+ + 답변 {question.answers?.length}개 +
+ )} +
+
+
+ +
+ + + + {/* 답변 영역 */} +
+
+

+ + 답변 {hasAnswers ? `(${question.answers?.length})` : ''} +

+ + {session?.user && ( + + + + + + + + {editingAnswerId ? '답변 수정' : '새 답변 작성'} + + + 질문에 대한 답변을 작성해주세요. 다른 사용자들이 이해하기 쉽도록 구체적으로 작성해주세요. + + + +
+ +
+ + + + + +
+
+ )} +
+ + {hasAnswers ? ( +
+ {question.answers?.map((answer, index) => ( + + +
+
+ + + + + + +
+
+ {answer.authorName ?? `사용자 ${answer.author}`} +
+
+ {format(new Date(answer.createdAt), "yyyy년 MM월 dd일 HH:mm")} +
+
+
+ + {session?.user?.id === answer.author && ( +
+ + + + + + + + 답변 삭제 + + 정말로 이 답변을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + + + + 취소 + handleDeleteAnswer(answer.id)} + className="bg-destructive hover:bg-destructive/90" + > + 삭제 + + + + +
+ )} +
+
+ +
+ + {/* 답변별 댓글 섹션 */} +
+ { + if (!session?.user?.id) return; + try { + await createComment({ content, answerId: answer.id }); + router.refresh(); + } catch (err) { + console.error("댓글 작성 실패:", err); + } + }} + onDeleteComment={async (commentId) => { + try { + await deleteComment(commentId); + router.refresh(); + } catch (err) { + console.error("댓글 삭제 실패:", err); + } + }} + onUpdateComment={async (commentId, content) => { + try { + await updateComment(commentId, content); + router.refresh(); + } catch (err) { + console.error("댓글 수정 실패:", err); + } + }} + /> +
+ + + ))} +
+ ) : ( + + + +

+ 아직 등록된 답변이 없습니다. +

+ {session?.user && ( + + )} +
+
+ )} +
+
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3