From e6874b96135d8eaccb5f207a194ff757bdbb9f36 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 16 May 2025 16:56:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20FAQ=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=9E=91=EC=97=85(cs,=20features)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pst/bbsfaq/[bbsId]/[pstNo]/page.tsx | 10 + .../pst/bbsfaq/[bbsId]/page.tsx | 10 + .../pst/bbsfaq/[bbsId]/register/page.tsx | 10 + .../features/src/pages/(pst)/faq/index.ts | 3 + .../src/pages/(pst)/faq/ui/create-page.tsx | 43 +++ .../src/pages/(pst)/faq/ui/faq-page.tsx | 316 +++++++++++++++++ .../src/pages/(pst)/faq/ui/modify-page.tsx | 53 +++ .../(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx | 10 + .../bbs/(post)/bbsfaq/[bbsId]/page.tsx | 10 + .../(post)/bbsfaq/[bbsId]/register/page.tsx | 10 + apps/cs/src/pages/(post)/faq/index.ts | 3 + .../src/pages/(post)/faq/ui/create-page.tsx | 43 +++ apps/cs/src/pages/(post)/faq/ui/faq-page.tsx | 318 ++++++++++++++++++ .../src/pages/(post)/faq/ui/modify-page.tsx | 53 +++ 14 files changed, 892 insertions(+) create mode 100644 apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx create mode 100644 apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx create mode 100644 apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx create mode 100644 apps/common/features/src/pages/(pst)/faq/index.ts create mode 100644 apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx create mode 100644 apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx create mode 100644 apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx create mode 100644 apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx create mode 100644 apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/page.tsx create mode 100644 apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/register/page.tsx create mode 100644 apps/cs/src/pages/(post)/faq/index.ts create mode 100644 apps/cs/src/pages/(post)/faq/ui/create-page.tsx create mode 100644 apps/cs/src/pages/(post)/faq/ui/faq-page.tsx create mode 100644 apps/cs/src/pages/(post)/faq/ui/modify-page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx new file mode 100644 index 000000000..9d3f6c0e3 --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx @@ -0,0 +1,10 @@ +import { ModifyPage } from "@/pages/(pst)/faq"; + +export default async function PstBbsFaqPostModifyPage({ + params, +}: { + params: Promise<{ bbsId: string; pstNo: string }>; +}) { + const { bbsId, pstNo } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx new file mode 100644 index 000000000..34dd522bf --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx @@ -0,0 +1,10 @@ +import { FaqPage } from "@/pages/(pst)/faq"; + +export default async function PstBbsFaqPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx new file mode 100644 index 000000000..6a044f876 --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx @@ -0,0 +1,10 @@ +import { CreatePage } from "@/pages/(pst)/faq"; + +export default async function PstBbsFaqRegisterPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/common/features/src/pages/(pst)/faq/index.ts b/apps/common/features/src/pages/(pst)/faq/index.ts new file mode 100644 index 000000000..350df198c --- /dev/null +++ b/apps/common/features/src/pages/(pst)/faq/index.ts @@ -0,0 +1,3 @@ +export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; +export { FaqPage } from "./ui/faq-page"; diff --git a/apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx b/apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx new file mode 100644 index 000000000..8b564cad4 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; +import { PostForm } from "@/widgets/post"; +import { PostApi, postFormSchema, type PostFormData } from "@/entities/post"; +import { useAlertStore, useUserStore } from "@/shared/lib"; + +function CreatePage({ bbsId }: { bbsId: string }) { + const router = useRouter(); + const { userInfo } = useUserStore((state) => state); + const { setMessage: alert } = useAlertStore((state) => state); + + const { mutateAsync: createPost } = useMutation({ + mutationFn: (data: PostFormData) => PostApi.createPost(data), + onSuccess: () => { + alert("등록되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(postFormSchema), + values: { + bbsId: parseInt(bbsId), + pstTtl: "", + wrtrNm: userInfo?.userName ?? "", + pstPswd: userInfo?.userUnqId ?? "", + }, + }); + + return ( +
+
+

FAQ 게시글 등록

+
+ +
+ ); +} + +export { CreatePage }; diff --git a/apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx b/apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx new file mode 100644 index 000000000..a5d7129d0 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx @@ -0,0 +1,316 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + Checkbox, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Skeleton, +} from "@common/components"; +import { AttachmentApi } from "@/entities/attachment"; +import { PostApi, type PostParams } from "@/entities/post"; +import { useAlertStore, useMenuStore, useUserStore } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import dayjs from "dayjs"; +import React from "react"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; + +function FaqPage({ bbsId }: { bbsId: string }) { + const router = useRouter(); + const pathname = usePathname(); + const { confirm } = useConfirm(); + const { userInfo } = useUserStore((state) => state); + const { setMessage: alert } = useAlertStore((state) => state); + const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state); + + const [postParams, setPostParams] = useState({ + keywordType: "title", + keyword: "", + }); + const [selectedPostNumbers, setSelectedPostNumbers] = useState([]); + + const { + data: postList, + isFetched, + refetch, + } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsInfo?.bbsTypeCd], + queryFn: () => + PostApi.getPostList(parseInt(bbsId), { + ...postParams, + bbsTypeCd: bbsInfo?.bbsTypeCd, + }), + enabled: !!bbsInfo?.bbsTypeCd, + }); + + const { mutate: deletePost } = useMutation({ + mutationFn: ({ pstNo, pstPswd }: { pstNo: number; pstPswd?: string }) => + PostApi.deletePost(parseInt(bbsId), pstNo, pstPswd), + onSuccess: () => { + alert("삭제되었습니다."); + refetch(); + }, + }); + + const handleDeletePost = async (pstNo: number) => { + if (await confirm("삭제하시겠습니까?")) { + deletePost({ pstNo, pstPswd: userInfo?.userUnqId }); + } + }; + + const onSelectedPostDelete = async () => { + if (await confirm("삭제하시겠습니까?")) { + await Promise.all( + selectedPostNumbers.map((pstNo) => + PostApi.deletePost(parseInt(bbsId), pstNo, userInfo?.userUnqId), + ), + ); + refetch(); + setSelectedPostNumbers([]); + alert("삭제되었습니다."); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + const handleCheckboxChange = (pstNo: number, checked: boolean | string) => { + setSelectedPostNumbers( + checked === true + ? [...selectedPostNumbers, pstNo] + : selectedPostNumbers.filter((num) => num !== pstNo), + ); + }; + + const handleAllCheckboxChange = (checked: boolean | string) => { + setSelectedPostNumbers( + checked === true ? postList?.content.map((item) => item.pstNo) || [] : [], + ); + }; + + return ( +
+
+
+ + + + setPostParams({ ...postParams, keyword: e.target.value }) + } + className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" + /> + +
+
+
+
+
+

콘텐츠 목록

+
+ {pageMenuAuthority?.regAuthrtYn === "Y" && ( + + )} + {pageMenuAuthority?.delAuthrtYn === "Y" && ( + + )} +
+
+
+
+
+ e.stopPropagation()} + onCheckedChange={handleAllCheckboxChange} + /> + +
+ {isFetched ? ( + + {postList?.content.map((item) => ( +
+ e.stopPropagation()} + onCheckedChange={(checked) => + handleCheckboxChange(item.pstNo, checked) + } + className="mx-2 my-6" + /> + + +
+
+ Q + {item.pstTtl} +
+ + 등록일: {dayjs(item.regDt).format("YYYY-MM-DD")} + +
+
+ +
+ + A + +
+
+                            {item.pstCn}
+                          
+ +
+
+ + +
+
+
+
+
+ ))} +
+ ) : ( + + )} +
+
+
+ ); +} + +function Attachment({ atflCd }: { atflCd: string }) { + const [filename, setFilename] = useState(""); + + const { data: downloadResponse, isFetching } = useQuery({ + queryKey: [ENDPOINT.ATTACH_SERVICE.DOWNLOAD, atflCd], + queryFn: () => AttachmentApi.downloadAttachmentByAtflCd(atflCd), + enabled: !!atflCd, + }); + + 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]); + + return isFetching ? ( +
+ +
+ ) : ( + filename && ( +
+ 첨부파일 + +
+ ) + ); +} + +export { FaqPage }; diff --git a/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx new file mode 100644 index 000000000..7f56031d9 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { PostForm } from "@/widgets/post"; +import { + PostApi, + postUpdateFormSchema, + type PostUpdateFormData, +} from "@/entities/post"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useUserStore } from "@/shared/lib"; + +function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + const { userInfo } = useUserStore((state) => state); + + const { data: postInfo } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], + queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + }); + + const { mutateAsync: updatePost } = useMutation({ + mutationFn: (data: PostUpdateFormData) => + PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data), + onSuccess: () => { + alert("수정되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(postUpdateFormSchema), + values: { + ...postInfo, + pstTtl: postInfo?.pstTtl ?? "", + pstPswd: userInfo?.userUnqId ?? "", + }, + }); + + return ( +
+
+

FAQ 게시글 수정

+
+ +
+ ); +} + +export { ModifyPage }; diff --git a/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx new file mode 100644 index 000000000..7c8d6ee86 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx @@ -0,0 +1,10 @@ +import { ModifyPage } from "@/pages/(post)/faq"; + +export default async function PstBbsFaqPostModifyPage({ + params, +}: { + params: Promise<{ bbsId: string; pstNo: string }>; +}) { + const { bbsId, pstNo } = await params; + return ; +} diff --git a/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/page.tsx b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/page.tsx new file mode 100644 index 000000000..12fe535a5 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/page.tsx @@ -0,0 +1,10 @@ +import { FaqPage } from "@/pages/(post)/faq"; + +export default async function PstBbsFaqPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/register/page.tsx b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/register/page.tsx new file mode 100644 index 000000000..e768a48db --- /dev/null +++ b/apps/cs/app/(with-base-layout)/bbs/(post)/bbsfaq/[bbsId]/register/page.tsx @@ -0,0 +1,10 @@ +import { CreatePage } from "@/pages/(post)/faq"; + +export default async function PstBbsFaqRegisterPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/cs/src/pages/(post)/faq/index.ts b/apps/cs/src/pages/(post)/faq/index.ts new file mode 100644 index 000000000..350df198c --- /dev/null +++ b/apps/cs/src/pages/(post)/faq/index.ts @@ -0,0 +1,3 @@ +export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; +export { FaqPage } from "./ui/faq-page"; diff --git a/apps/cs/src/pages/(post)/faq/ui/create-page.tsx b/apps/cs/src/pages/(post)/faq/ui/create-page.tsx new file mode 100644 index 000000000..8b564cad4 --- /dev/null +++ b/apps/cs/src/pages/(post)/faq/ui/create-page.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; +import { PostForm } from "@/widgets/post"; +import { PostApi, postFormSchema, type PostFormData } from "@/entities/post"; +import { useAlertStore, useUserStore } from "@/shared/lib"; + +function CreatePage({ bbsId }: { bbsId: string }) { + const router = useRouter(); + const { userInfo } = useUserStore((state) => state); + const { setMessage: alert } = useAlertStore((state) => state); + + const { mutateAsync: createPost } = useMutation({ + mutationFn: (data: PostFormData) => PostApi.createPost(data), + onSuccess: () => { + alert("등록되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(postFormSchema), + values: { + bbsId: parseInt(bbsId), + pstTtl: "", + wrtrNm: userInfo?.userName ?? "", + pstPswd: userInfo?.userUnqId ?? "", + }, + }); + + return ( +
+
+

FAQ 게시글 등록

+
+ +
+ ); +} + +export { CreatePage }; diff --git a/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx b/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx new file mode 100644 index 000000000..969c905de --- /dev/null +++ b/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx @@ -0,0 +1,318 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + Checkbox, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Skeleton, +} from "@common/components"; +import { AttachmentApi } from "@/entities/attachment"; +import { PostApi, type PostParams } from "@/entities/post"; +import { useAlertStore, useMenuStore, usePrompt } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import dayjs from "dayjs"; +import React from "react"; + +function FaqPage({ bbsId }: { bbsId: string }) { + const router = useRouter(); + const pathname = usePathname(); + const { prompt } = usePrompt(); + const { setMessage: alert } = useAlertStore((state) => state); + const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state); + + const [postParams, setPostParams] = useState({ + keywordType: "title", + keyword: "", + }); + const [selectedPostNumbers, setSelectedPostNumbers] = useState([]); + + const { + data: postList, + isFetched, + refetch, + } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsInfo?.bbsTypeCd], + queryFn: () => + PostApi.getPostList(parseInt(bbsId), { + ...postParams, + bbsTypeCd: bbsInfo?.bbsTypeCd, + }), + enabled: !!bbsInfo?.bbsTypeCd, + }); + + const { mutate: deletePost } = useMutation({ + mutationFn: ({ pstNo, delRsn }: { pstNo: number; delRsn?: string }) => + PostApi.deletePost(parseInt(bbsId), pstNo, delRsn), + onSuccess: () => { + alert("삭제되었습니다."); + refetch(); + }, + }); + + const getPromptValue = async (message: string) => { + const { resolved, value } = await prompt(message); + if (!resolved) return ""; + return value; + }; + + const handleDeletePost = async (pstNo: number) => { + const delRsn = await getPromptValue("삭제 사유를 입력해주세요."); + deletePost({ pstNo, delRsn }); + }; + + const onSelectedPostDelete = async () => { + const delRsn = await getPromptValue("삭제 사유를 입력해주세요."); + await Promise.all( + selectedPostNumbers.map((pstNo) => + PostApi.deletePost(parseInt(bbsId), pstNo, delRsn), + ), + ); + refetch(); + setSelectedPostNumbers([]); + alert("삭제되었습니다."); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + const handleCheckboxChange = (pstNo: number, checked: boolean | string) => { + setSelectedPostNumbers( + checked === true + ? [...selectedPostNumbers, pstNo] + : selectedPostNumbers.filter((num) => num !== pstNo), + ); + }; + + const handleAllCheckboxChange = (checked: boolean | string) => { + setSelectedPostNumbers( + checked === true ? postList?.content.map((item) => item.pstNo) || [] : [], + ); + }; + + return ( +
+
+
+ + + + setPostParams({ ...postParams, keyword: e.target.value }) + } + className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" + /> + +
+
+
+
+
+

콘텐츠 목록

+
+ {pageMenuAuthority?.regAuthrtYn === "Y" && ( + + )} + {pageMenuAuthority?.delAuthrtYn === "Y" && ( + + )} +
+
+
+
+
+ e.stopPropagation()} + onCheckedChange={handleAllCheckboxChange} + /> + +
+ {isFetched ? ( + + {postList?.content.map((item) => ( +
+ e.stopPropagation()} + onCheckedChange={(checked) => + handleCheckboxChange(item.pstNo, checked) + } + className="mx-2 my-6" + /> + + +
+
+ Q + {item.pstTtl} +
+ + 등록일: {dayjs(item.regDt).format("YYYY-MM-DD")} + +
+
+ +
+ + A + +
+
+                            {item.pstCn}
+                          
+ +
+
+ + +
+
+
+
+
+ ))} +
+ ) : ( + + )} +
+
+
+ ); +} + +function Attachment({ atflCd }: { atflCd: string }) { + const [filename, setFilename] = useState(""); + + const { data: downloadResponse, isFetching } = useQuery({ + queryKey: [ENDPOINT.ATTACH_SERVICE.DOWNLOAD, atflCd], + queryFn: () => AttachmentApi.downloadAttachmentByAtflCd(atflCd), + enabled: !!atflCd, + }); + + 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]); + + return isFetching ? ( +
+ +
+ ) : ( + filename && ( +
+ 첨부파일 + +
+ ) + ); +} + +export { FaqPage }; diff --git a/apps/cs/src/pages/(post)/faq/ui/modify-page.tsx b/apps/cs/src/pages/(post)/faq/ui/modify-page.tsx new file mode 100644 index 000000000..1a0ed8006 --- /dev/null +++ b/apps/cs/src/pages/(post)/faq/ui/modify-page.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { PostForm } from "@/widgets/post"; +import { + PostApi, + postUpdateFormSchema, + type PostUpdateFormData, +} from "@/entities/post"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useUserStore } from "@/shared/lib"; + +function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + const { userInfo } = useUserStore((state) => state); + + const { data: postInfo } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo], + queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + }); + + const { mutateAsync: updatePost } = useMutation({ + mutationFn: (data: PostUpdateFormData) => + PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data), + onSuccess: () => { + alert("수정되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(postUpdateFormSchema), + values: { + ...postInfo, + pstTtl: postInfo?.pstTtl ?? "", + pstPswd: userInfo?.userUnqId ?? "", + }, + }); + + return ( +
+
+

FAQ 게시글 수정

+
+ +
+ ); +} + +export { ModifyPage };