270 lines
8.9 KiB
TypeScript
270 lines
8.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import dayjs from "dayjs";
|
|
|
|
import { ENDPOINT } from "@/shared/config";
|
|
import { useAlertStore, useMenuStore, useMutationWrapper } from "@/shared/lib";
|
|
import { AttachmentApi } from "@/entities/attachment";
|
|
import { BoardApi } from "@/entities/board";
|
|
import { PostApi } from "@/entities/post";
|
|
import {
|
|
Button,
|
|
Label,
|
|
RadioGroup,
|
|
RadioGroupItem,
|
|
Skeleton,
|
|
} from "@common/components";
|
|
|
|
import { PostEvaluationApi } from "../api/post-evaluation-service";
|
|
|
|
import { PostAction } from "./post-action";
|
|
import { PostComment } from "./post-comment";
|
|
|
|
function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
|
const router = useRouter();
|
|
const alert = useAlertStore((state) => state.setMessage);
|
|
const { bbsInfo, setBbsInfo } = useMenuStore((state) => state);
|
|
|
|
const [filename, setFilename] = useState("");
|
|
const [evlScr, setEvlScr] = useState(5);
|
|
|
|
const { data: boardInfo } = useQuery({
|
|
queryKey: [ENDPOINT.BOARD_SERVICE.BBS, bbsId],
|
|
queryFn: () => BoardApi.getBoardInfo(bbsId),
|
|
});
|
|
|
|
const { data: postData } = useQuery({
|
|
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
|
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
|
});
|
|
|
|
const { data: downloadResponse, isFetching } = useQuery({
|
|
queryKey: [ENDPOINT.ATTACH_SERVICE.DOWNLOAD, postData?.atflCd],
|
|
queryFn: () =>
|
|
AttachmentApi.downloadAttachmentByAtflCd(postData?.atflCd as string),
|
|
enabled:
|
|
!!postData?.atflCd &&
|
|
bbsInfo?.bbsTypeCd !== "BBSIMAGE" &&
|
|
bbsInfo?.bbsTypeCd !== "BBSMOVIE",
|
|
});
|
|
|
|
const { data: imageUrl, isLoading } = useQuery({
|
|
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, postData?.atflCd],
|
|
queryFn: () =>
|
|
AttachmentApi.getImageUrlByAtflCd(postData?.atflCd as string),
|
|
enabled: !!postData?.atflCd,
|
|
});
|
|
|
|
const { data: videoAttachment } = useQuery({
|
|
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, postData?.atflCd],
|
|
queryFn: () => AttachmentApi.getAttachmentsInfo(postData?.atflCd as string),
|
|
select: (data) => data[0],
|
|
enabled: !!postData?.atflCd && bbsInfo?.bbsTypeCd === "BBSMOVIE",
|
|
});
|
|
|
|
const { data: videoUrl } = useQuery({
|
|
queryKey: [ENDPOINT.ATTACH_SERVICE.STREAM_VIDEO, videoAttachment?.atflId],
|
|
queryFn: () =>
|
|
AttachmentApi.getStreamVideoUrlByAtflId(
|
|
videoAttachment?.atflId as string,
|
|
),
|
|
enabled: !!videoAttachment?.atflId,
|
|
});
|
|
|
|
const { mutateAsync: deletePost } = useMutationWrapper({
|
|
mutationFn: (delRsn?: string) =>
|
|
PostApi.deletePost(parseInt(bbsId), parseInt(pstNo), delRsn),
|
|
onSuccess: () => {
|
|
alert("삭제되었습니다.", router.back);
|
|
},
|
|
});
|
|
|
|
const { mutate: postEvaluation } = useMutationWrapper({
|
|
mutationFn: (evlScr: number) =>
|
|
PostEvaluationApi.postEvaluation({
|
|
pstNo: parseInt(pstNo),
|
|
evlScr,
|
|
}),
|
|
onSuccess: () => {
|
|
alert("게시물 평가가 완료되었습니다.");
|
|
},
|
|
});
|
|
|
|
const handleDownload = async () => {
|
|
if (!downloadResponse) return;
|
|
try {
|
|
const blob = new Blob([downloadResponse.data]);
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
|
|
link.href = url;
|
|
link.download = filename || "download";
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(link);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!downloadResponse) return;
|
|
try {
|
|
let filename = "";
|
|
const contentDisposition =
|
|
downloadResponse.headers["content-disposition"];
|
|
if (contentDisposition) {
|
|
const filenameMatch = contentDisposition.match(
|
|
/filename\*=UTF-8''([^;]+)/,
|
|
);
|
|
if (filenameMatch && filenameMatch[1]) {
|
|
filename = decodeURIComponent(filenameMatch[1]);
|
|
}
|
|
}
|
|
setFilename(filename);
|
|
} catch {
|
|
setFilename("");
|
|
}
|
|
}, [downloadResponse]);
|
|
|
|
useEffect(() => {
|
|
if (!boardInfo) return;
|
|
setBbsInfo(boardInfo);
|
|
}, [boardInfo]);
|
|
|
|
return (
|
|
<>
|
|
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={deletePost}>
|
|
<div className="card card-border !p-8">
|
|
<h3 className="text-[2rem] font-bold text-foreground">
|
|
{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" />
|
|
{bbsInfo?.bbsTypeCd === "BBSIMAGE" && (
|
|
<>
|
|
{isLoading && (
|
|
<div className="relative my-6 flex h-[75rem] w-[100rem]">
|
|
<Skeleton className="h-full w-full" />
|
|
</div>
|
|
)}
|
|
{imageUrl && (
|
|
<div className="relative my-6 flex h-[75rem] w-[100rem]">
|
|
<Image
|
|
src={imageUrl}
|
|
className="object-cover"
|
|
fill
|
|
alt="썸네일 이미지"
|
|
/>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
{bbsInfo?.bbsTypeCd === "BBSMOVIE" && (
|
|
<div className="relative my-6 flex h-[56.25rem] w-[100rem]">
|
|
<video src={videoUrl} controls className="object-contain" />
|
|
</div>
|
|
)}
|
|
<pre className="min-h-[40rem] whitespace-pre-wrap p-8 font-noto text-2xl tracking-tighter text-foreground">
|
|
{postData?.pstCn ?? ""}
|
|
</pre>
|
|
{isFetching ? (
|
|
<div className="p-8">
|
|
<Skeleton className="h-[2.5rem] w-[32rem]" />
|
|
</div>
|
|
) : (
|
|
filename && (
|
|
<>
|
|
<hr />
|
|
<div className="flex items-center gap-4 p-8">
|
|
<span className="font-bold text-label">첨부파일</span>
|
|
<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>
|
|
</>
|
|
)
|
|
)}
|
|
{bbsInfo?.dgstfnEvlUseYn === "Y" && (
|
|
<>
|
|
<hr />
|
|
<div className="mt-8 flex items-center gap-2 px-4">
|
|
<p>
|
|
이 페이지에서 제공하는
|
|
<br />
|
|
정보에 대하여 만족하시나요?
|
|
</p>
|
|
<div className="ml-4 rounded-sm border border-border bg-accent px-8 py-4">
|
|
<RadioGroup
|
|
value={evlScr.toString()}
|
|
onValueChange={(value) => setEvlScr(parseInt(value))}
|
|
className="flex items-center space-x-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="5" id="evl5" />
|
|
<Label htmlFor="evl5">매우 만족 😊</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="4" id="evl4" />
|
|
<Label htmlFor="evl4">만족 🙂</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="3" id="evl3" />
|
|
<Label htmlFor="evl3">보통 😐</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="2" id="evl2" />
|
|
<Label htmlFor="evl2">불만족 🙁</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="1" id="evl1" />
|
|
<Label htmlFor="evl1">매우 불만족 😞</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
<Button
|
|
size="lg"
|
|
color="white"
|
|
onClick={() => postEvaluation(evlScr)}
|
|
>
|
|
평가하기
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</PostAction>
|
|
{bbsInfo?.cmntUseYn === "Y" && (
|
|
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export { Post };
|