feat: 게시글 댓글 등록 작업

This commit is contained in:
2025-05-09 17:25:59 +09:00
parent e675e4cff6
commit 697ac34f65
10 changed files with 314 additions and 42 deletions
@@ -0,0 +1,147 @@
import dayjs from "dayjs";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Button, Input, Textarea } from "@common/components";
import {
CommentApi,
commentFormSchema,
type CommentFormData,
} from "@/entities/comment";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import { useAlertStore, useUserStore } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
function PostComment({ pstNo }: { pstNo: number }) {
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const { userInfo } = useUserStore((state) => state);
const { data: comments } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
queryFn: () => CommentApi.getComments({ pstNo }),
enabled: !!pstNo,
});
const { mutate: createComment } = useMutation({
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
onSuccess: () => {
alert("댓글이 등록되었습니다.");
form.setValue("cmntCn", "");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
const form = useForm<CommentFormData>({
resolver: zodResolver(commentFormSchema),
values: {
pstNo,
cmntCn: "",
wrtrNm: userInfo?.userName ?? "",
cmntPswd: userInfo?.userUnqId ?? "",
},
});
const onSubmit = (data: CommentFormData) => {
createComment(data);
};
return (
<div className="card card-border !p-8">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="cmntCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
maxLength={1000}
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
placeholder="댓글을 입력해주세요."
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<div className="mt-4 flex items-center justify-between">
<div className="flex gap-4">
{!userInfo?.userName && (
<FormField
control={form.control}
name="wrtrNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[16rem] bg-background !text-[1.3rem]"
placeholder="작성자"
/>
</FormControl>
</FormItem>
)}
/>
)}
{!userInfo?.userUnqId && (
<FormField
control={form.control}
name="cmntPswd"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
type="password"
value={field.value ?? ""}
className="w-[12rem] bg-background !text-[1.3rem]"
placeholder="비밀번호"
/>
</FormControl>
</FormItem>
)}
/>
)}
</div>
<Button type="submit">등록</Button>
</div>
</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>
</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>
);
}
export { PostComment };
@@ -8,6 +8,7 @@ import { AttachmentApi } from "@/entities/attachment";
import { PostApi } from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { PostAction } from "./post-action";
import { PostComment } from "./post-comment";
function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const [filename, setFilename] = useState("");
@@ -64,48 +65,51 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
};
return (
<PostAction bbsId={bbsId} pstNo={pstNo}>
<div className="card card-border !p-8">
<h3 className="text-[2rem] font-bold">{postData?.pstTtl}</h3>
<div className="card !m-0 !mt-8 bg-accent">
<ul className="flex gap-8 p-4 text-xl text-label">
<li>
작성자: <b>{postData?.wrtrNm}</b>
</li>
<li>
등록일: <b>{dayjs(postData?.regDt).format("YYYY-MM-DD")}</b>
</li>
<li>
조회수: <b>{postData?.inqNocs}</b>
</li>
</ul>
<>
<PostAction bbsId={bbsId} pstNo={pstNo}>
<div className="card card-border !p-8">
<h3 className="text-[2rem] font-bold">{postData?.pstTtl}</h3>
<div className="card !m-0 !mt-8 bg-accent">
<ul className="flex gap-8 p-4 text-xl text-label">
<li>
작성자: <b>{postData?.wrtrNm}</b>
</li>
<li>
등록일: <b>{dayjs(postData?.regDt).format("YYYY-MM-DD")}</b>
</li>
<li>
조회수: <b>{postData?.inqNocs}</b>
</li>
</ul>
</div>
<hr className="mt-8" />
<div
className="min-h-[40rem] p-8"
dangerouslySetInnerHTML={{ __html: postData?.pstCn ?? "" }}
/>
<hr className="mt-8" />
<div className="flex items-center gap-4 p-8">
<span className="font-bold">첨부파일</span>
{isFetching ? (
<Skeleton className="h-[2.5rem] w-[20rem]" />
) : (
filename && (
<Button
size="sm"
color="white"
className="flex w-fit items-center gap-2"
onClick={handleDownload}
>
<i className="diveicon di-download" />
<span>{filename}</span>
</Button>
)
)}
</div>
</div>
<hr className="mt-8" />
<div
className="min-h-[40rem] p-8"
dangerouslySetInnerHTML={{ __html: postData?.pstCn ?? "" }}
/>
<hr className="mt-8" />
<div className="flex items-center gap-4 p-8">
<span className="font-bold">첨부파일</span>
{isFetching ? (
<Skeleton className="h-[2.5rem] w-[20rem]" />
) : (
filename && (
<Button
size="sm"
color="white"
className="flex w-fit items-center gap-2"
onClick={handleDownload}
>
<i className="diveicon di-download" />
<span>{filename}</span>
</Button>
)
)}
</div>
</div>
</PostAction>
</PostAction>
<PostComment pstNo={parseInt(pstNo)} />
</>
);
}