feat: 댓글 수정, 삭제 기능

This commit is contained in:
2025-05-12 14:25:38 +09:00
parent 1a8053910d
commit 8b265a7aae
5 changed files with 182 additions and 42 deletions
@@ -1,21 +1,28 @@
import dayjs from "dayjs";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Button, Input, Textarea } from "@common/components";
import {
CommentApi,
commentFormSchema,
type CommentFormData,
type CommentUpdateFormData,
} from "@/entities/comment";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { useAlertStore, useUserStore } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, usePrompt, useUserStore } from "@/shared/lib";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
function PostComment({ pstNo }: { pstNo: number }) {
function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
const { prompt } = usePrompt();
const { confirm } = useConfirm();
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const { userInfo } = useUserStore((state) => state);
const { setMessage: alert } = useAlertStore((state) => state);
const [editingCmntNo, setEditingCmntNo] = useState<number | null>(null);
const [editValue, setEditValue] = useState<string>("");
const { data: comments } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
@@ -26,7 +33,7 @@ function PostComment({ pstNo }: { pstNo: number }) {
const { mutate: createComment } = useMutation({
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
onSuccess: () => {
alert("댓글이 등록되었습니다.");
alert("등록되었습니다.");
form.setValue("cmntCn", "");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
@@ -34,6 +41,35 @@ function PostComment({ pstNo }: { pstNo: number }) {
},
});
const { mutate: updateComment } = useMutation({
mutationFn: ({
cmntNo,
data,
}: {
cmntNo: number;
data: CommentUpdateFormData;
}) => CommentApi.updateComment(bbsId, pstNo, cmntNo, data),
onSuccess: () => {
alert("수정되었습니다.");
setEditingCmntNo(null);
setEditValue("");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
const { mutate: deleteComment } = useMutation({
mutationFn: ({ cmntNo, cmntPswd }: { cmntNo: number; cmntPswd: string }) =>
CommentApi.deleteComment(bbsId, pstNo, cmntNo, cmntPswd),
onSuccess: () => {
alert("삭제되었습니다.");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
const form = useForm<CommentFormData>({
resolver: zodResolver(commentFormSchema),
values: {
@@ -48,6 +84,35 @@ function PostComment({ pstNo }: { pstNo: number }) {
createComment(data);
};
const getCommentPassword = async () => {
let cmntPswd = "";
if (userInfo?.userUnqId) {
cmntPswd = userInfo?.userUnqId;
} else {
cmntPswd = await prompt("비밀번호를 입력해주세요.");
}
return cmntPswd;
};
const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => {
if (!(await confirm("수정하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
updateComment({
cmntNo,
data: {
cmntCn: editValue,
wrtrNm,
cmntPswd,
},
});
};
const handleDeleteComment = async (cmntNo: number) => {
if (!(await confirm("삭제하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
deleteComment({ cmntNo, cmntPswd });
};
return (
<div className="card card-border !p-8">
<Form {...form}>
@@ -113,32 +178,77 @@ function PostComment({ pstNo }: { pstNo: number }) {
</form>
</Form>
<div className="mt-8 flex flex-col">
{comments?.content.map((comment) => (
<div
className="flex justify-between gap-2 border-t border-gray-200 py-8"
key={comment.cmntNo}
>
<div className="flex gap-10">
<div className="flex w-[10rem] shrink-0 flex-col">
<span className="text-[1.4rem] font-bold">
{comment.wrtrNm}
</span>
<span className="text-[1.2rem] text-gray-500">
{dayjs(comment.regDt).format("YYYY.MM.DD")}
</span>
{comments?.content.map((comment) => {
if (comment.delYn === "Y") return null;
return (
<div
className="flex justify-between gap-2 border-t border-gray-200 py-8"
key={comment.cmntNo}
>
<div className="flex w-full gap-10">
<div className="flex w-[10rem] shrink-0 flex-col">
<span className="text-[1.4rem] font-bold">
{comment.wrtrNm}
</span>
<span className="text-[1.2rem] text-gray-500">
{dayjs(comment.regDt).format("YYYY.MM.DD")}
</span>
</div>
{editingCmntNo === comment.cmntNo ? (
<div className="flex flex-1 flex-col gap-2">
<Textarea
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
maxLength={1000}
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
/>
<div className="flex gap-2">
<Button
size="sm"
onClick={() =>
handleUpdateComment(comment.cmntNo, comment.wrtrNm)
}
>
저장
</Button>
<Button
size="sm"
color="gray"
onClick={() => {
setEditingCmntNo(null);
setEditValue("");
}}
>
취소
</Button>
</div>
</div>
) : (
<p className="break-all text-[1.4rem]">{comment.cmntCn}</p>
)}
</div>
<div className="flex gap-2 whitespace-nowrap">
<Button
size="icon"
color="white"
onClick={() => {
setEditingCmntNo(comment.cmntNo);
setEditValue(comment.cmntCn);
}}
>
<i className="diveicon di-pencil" />
</Button>
<Button
size="icon"
color="white"
onClick={() => handleDeleteComment(comment.cmntNo)}
>
<i className="diveicon di-trash" />
</Button>
</div>
<p className="break-all text-[1.4rem]">{comment.cmntCn}</p>
</div>
<div className="flex gap-2 whitespace-nowrap">
<Button size="icon" color="white">
<i className="diveicon di-pencil" />
</Button>
<Button size="icon" color="white">
<i className="diveicon di-trash" />
</Button>
</div>
</div>
))}
);
})}
</div>
</div>
);
@@ -131,7 +131,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
</div>
</div>
</PostAction>
<PostComment pstNo={parseInt(pstNo)} />
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
</>
);
}