diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx new file mode 100644 index 000000000..d5ab3ee62 --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx @@ -0,0 +1,10 @@ +import { ModifyPage } from "@/pages/(pst)/normal"; + +export default async function PstBbsNormalPostModifyPage({ + params, +}: { + params: Promise<{ bbsId: string; pstNo: string }>; +}) { + const { bbsId, pstNo } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx new file mode 100644 index 000000000..aa2120e7c --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx @@ -0,0 +1,10 @@ +import { NormalPost } from "@/pages/(pst)/normal"; + +export default async function PstBbsNormalPostPage({ + params, +}: { + params: Promise<{ bbsId: string; pstNo: string }>; +}) { + const { bbsId, pstNo } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx index e69de29bb..eecbc7c33 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx @@ -0,0 +1,10 @@ +import { NormalPage } from "@/pages/(pst)/normal"; + +export default async function PstBbsNormalPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx new file mode 100644 index 000000000..122c87dc4 --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx @@ -0,0 +1,10 @@ +import { CreatePage } from "@/pages/(pst)/normal"; + +export default async function PstBbsNormalRegisterPage({ + params, +}: { + params: Promise<{ bbsId: string }>; +}) { + const { bbsId } = await params; + return ; +} diff --git a/apps/common/features/src/entities/board/index.ts b/apps/common/features/src/entities/board/index.ts index f9ef1b610..0b63103e7 100644 --- a/apps/common/features/src/entities/board/index.ts +++ b/apps/common/features/src/entities/board/index.ts @@ -1,8 +1,10 @@ export { BoardApi } from "./api/board-service"; export { boardFormSchema, + BOARD_POSTING_CODE, type Board, type BoardFormData, type BoardParams, type BoardPopup, + type BoardPostingCode, } from "./model/board-interface"; diff --git a/apps/common/features/src/entities/board/model/board-interface.ts b/apps/common/features/src/entities/board/model/board-interface.ts index 1ed8f1d9f..565b998fb 100644 --- a/apps/common/features/src/entities/board/model/board-interface.ts +++ b/apps/common/features/src/entities/board/model/board-interface.ts @@ -8,7 +8,15 @@ export const BOARD_TYPE = { BBSMOVIE: "BBSMOVIE", } as const; +export const BOARD_POSTING_CODE = { + 팝업게시: "01", + 상단게시: "02", + 기간게시: "03", +} as const; + export type BoardType = (typeof BOARD_TYPE)[keyof typeof BOARD_TYPE]; +export type BoardPostingCode = + (typeof BOARD_POSTING_CODE)[keyof typeof BOARD_POSTING_CODE]; export interface BoardParams extends PageableParams { keywordType?: string; diff --git a/apps/common/features/src/entities/post/model/post-interface.ts b/apps/common/features/src/entities/post/model/post-interface.ts index a988c51e2..09f08ac31 100644 --- a/apps/common/features/src/entities/post/model/post-interface.ts +++ b/apps/common/features/src/entities/post/model/post-interface.ts @@ -36,14 +36,24 @@ export interface PostInfo extends Post { mdfr: string; } +const postEtcSchema = z.object({ + bbsPstgTypeCd: z.string(), + pstgPrdSeCd: z.string(), + pstgBgngDt: z.string().nullish(), + pstgEndDt: z.string().nullish(), +}); + export const postFormSchema = z.object({ bbsId: z.number(), pstTtl: z.string().nonempty(), pstCn: z.string().nullish(), rfrncUrl: z.string().nullish(), popupYn: z.string().nullish(), + popupYnEtc: z.array(postEtcSchema).nullish(), upndFixYn: z.string().nullish(), + upndFixYnEtc: z.array(postEtcSchema).nullish(), prdPstgYn: z.string().nullish(), + prdPstgYnEtc: z.array(postEtcSchema).nullish(), pstPswd: z.string(), wrtrNm: z.string().nullish(), wrtrCplc: z.string().nullish(), diff --git a/apps/common/features/src/pages/(pst)/normal/index.ts b/apps/common/features/src/pages/(pst)/normal/index.ts new file mode 100644 index 000000000..bda06e816 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/normal/index.ts @@ -0,0 +1,4 @@ +export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; +export { NormalPage } from "./ui/normal-page"; +export { NormalPost } from "./ui/normal-post"; diff --git a/apps/common/features/src/pages/(pst)/normal/ui/create-page.tsx b/apps/common/features/src/pages/(pst)/normal/ui/create-page.tsx new file mode 100644 index 000000000..a6a926c32 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/normal/ui/create-page.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect } from "react"; +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 { setCookie, 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 ?? "", + popupYn: "N", + upndFixYn: "N", + prdPstgYn: "N", + }, + }); + + useEffect(() => { + setCookie("atfl-cd-key", ""); + }, []); + + return ( +
+
+

게시글 등록

+
+ +
+ ); +} + +export { CreatePage }; diff --git a/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx new file mode 100644 index 000000000..897b72e62 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx @@ -0,0 +1,51 @@ +"use client"; + +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 { 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(bbsId, pstNo), + }); + + const { mutateAsync: updatePost } = useMutation({ + mutationFn: (data: PostUpdateFormData) => + PostApi.updatePost(bbsId, pstNo, data), + onSuccess: () => { + alert("수정되었습니다."); + }, + }); + + const form = useForm({ + resolver: zodResolver(postUpdateFormSchema), + values: { + ...postInfo, + pstTtl: postInfo?.pstTtl ?? "", + pstPswd: userInfo?.userUnqId ?? "", + }, + }); + + return ( +
+
+

게시글 수정

+
+ +
+ ); +} + +export { ModifyPage }; diff --git a/apps/common/features/src/pages/(pst)/normal/ui/normal-page.tsx b/apps/common/features/src/pages/(pst)/normal/ui/normal-page.tsx new file mode 100644 index 000000000..22343dc93 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/normal/ui/normal-page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { PostPage as NormalPage } from "@/widgets/post"; diff --git a/apps/common/features/src/pages/(pst)/normal/ui/normal-post.tsx b/apps/common/features/src/pages/(pst)/normal/ui/normal-post.tsx new file mode 100644 index 000000000..3643fdc36 --- /dev/null +++ b/apps/common/features/src/pages/(pst)/normal/ui/normal-post.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { Post as NormalPost } from "@/widgets/post"; diff --git a/apps/common/features/src/pages/(pst)/notice/index.ts b/apps/common/features/src/pages/(pst)/notice/index.ts index 13b188dcf..2de5491f8 100644 --- a/apps/common/features/src/pages/(pst)/notice/index.ts +++ b/apps/common/features/src/pages/(pst)/notice/index.ts @@ -1,4 +1,4 @@ +export { CreatePage } from "./ui/create-page"; +export { ModifyPage } from "./ui/modify-page"; export { NoticePage } from "./ui/notice-page"; export { NoticePost } from "./ui/notice-post"; -export { ModifyPage } from "./ui/modify-page"; -export { CreatePage } from "./ui/create-page"; diff --git a/apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx b/apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx index a023a096a..4128d6107 100644 --- a/apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx +++ b/apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx @@ -1,152 +1,3 @@ "use client"; -import { useEffect, useState } from "react"; -import { usePathname, useRouter } from "next/navigation"; -import { useQuery } from "@tanstack/react-query"; -import { - getCoreRowModel, - useReactTable, - type PaginationState, - type RowSelectionState, -} from "@tanstack/react-table"; -import { - Button, - DataTable, - Input, - Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@common/components"; -import { PostApi, type Post, type PostParams } from "@/entities/post"; -import { ENDPOINT } from "@/shared/config"; -import { useMenuStore } from "@/shared/lib"; -import { columns } from "../model/notice-table-columns"; - -function NoticePage({ bbsId }: { bbsId: string }) { - const router = useRouter(); - const pathname = usePathname(); - const { pageMenuAuthority } = useMenuStore((state) => state); - - const [rowSelection, setRowSelection] = useState({}); - const [postParams, setPostParams] = useState({ - keywordType: "title", - keyword: "", - }); - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }); - - const { data: postList, refetch } = useQuery({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId], - queryFn: () => - PostApi.getPostList(bbsId, { - ...postParams, - page: pagination.pageIndex, - size: pagination.pageSize, - }), - enabled: false, - }); - - const table = useReactTable({ - data: postList?.content ?? [], - columns, - state: { - pagination, - rowSelection, - }, - rowCount: postList?.totalElements ?? 0, - manualPagination: true, - enableMultiRowSelection: true, - getCoreRowModel: getCoreRowModel(), - onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, - }); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - refetch(); - }; - - const handleRowClick = (row: Post) => { - router.push(`${pathname}/${row.pstNo}`); - }; - - useEffect(() => { - refetch(); - }, [pagination]); - - return ( -
-
-
- - - - setPostParams({ ...postParams, keyword: e.target.value }) - } - className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" - /> - -
-
-
-
-
-

공지사항 목록

-
- {pageMenuAuthority?.regAuthrtYn === "Y" && ( - - )} - -
-
-
-
- -
-
-
- ); -} - -export { NoticePage }; +export { PostPage as NoticePage } from "@/widgets/post"; diff --git a/apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx b/apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx index c61c734f3..a774212ef 100644 --- a/apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx +++ b/apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx @@ -1,9 +1,3 @@ "use client"; -import { Post } from "@/widgets/post"; - -function NoticePost({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { - return ; -} - -export { NoticePost }; +export { Post as NoticePost } from "@/widgets/post"; diff --git a/apps/common/features/src/widgets/post/index.ts b/apps/common/features/src/widgets/post/index.ts index 743492614..7d43c205d 100644 --- a/apps/common/features/src/widgets/post/index.ts +++ b/apps/common/features/src/widgets/post/index.ts @@ -1,2 +1,3 @@ export { Post } from "./ui/post"; export { PostForm } from "./ui/post-form"; +export { PostPage } from "./ui/post-page"; diff --git a/apps/common/features/src/widgets/post/model/post-table-columns.tsx b/apps/common/features/src/widgets/post/model/post-table-columns.tsx new file mode 100644 index 000000000..153593607 --- /dev/null +++ b/apps/common/features/src/widgets/post/model/post-table-columns.tsx @@ -0,0 +1,103 @@ +import dayjs from "dayjs"; +import type { ColumnDef } from "@tanstack/react-table"; +import { Badge, Button } from "@common/components"; +import { AttachmentApi } from "@/entities/attachment"; +import type { Post } from "@/entities/post"; + +export const columns: ColumnDef[] = [ + { + id: "no", + header: "NO", + cell: ({ row }) => { + if (row.original.isUpndFixPost) { + return ( + + + + ); + } + return row.index + 1; + }, + }, + { + accessorKey: "pstTtl", + header: "제목", + cell: ({ row }) => { + const { pstTtl, inqNocs } = row.original; + + return ( +
+ {inqNocs >= 100 && HOT} + {dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && ( + NEW + )} + {pstTtl} +
+ ); + }, + }, + { + accessorKey: "inqNocs", + header: "조회수", + }, + { + accessorKey: "atflCd", + header: "첨부파일", + cell: ({ row }) => { + const atflCd = row.original.atflCd; + if (!atflCd) return null; + return ; + }, + }, + { + accessorKey: "rgtrNm", + header: "작성자", + }, + { + accessorKey: "regDt", + header: "등록일", + cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"), + }, +]; + +const AttachmentButton = ({ atflCd }: { atflCd: string }) => { + const handleDownload = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + let filename = ""; + const response = await AttachmentApi.downloadAttachmentByAtflCd(atflCd); + const contentDisposition = response.headers["content-disposition"]; + if (contentDisposition) { + const filenameMatch = contentDisposition.match( + /filename\*=UTF-8''([^;]+)/, + ); + if (filenameMatch) { + filename = decodeURIComponent(filenameMatch[1]); + } + } + + const blob = new Blob([response.data]); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(link); + } catch (error) { + console.error(error); + } + }; + + return ( + + ); +}; diff --git a/apps/common/features/src/widgets/post/ui/post-form.tsx b/apps/common/features/src/widgets/post/ui/post-form.tsx index 19963f41c..fc3912780 100644 --- a/apps/common/features/src/widgets/post/ui/post-form.tsx +++ b/apps/common/features/src/widgets/post/ui/post-form.tsx @@ -20,8 +20,8 @@ import { Button, } from "@common/components"; import { CodeSelect } from "@/entities/code"; -import type { Board } from "@/entities/board"; import { handleTusUpload } from "@/entities/tus"; +import { BOARD_POSTING_CODE, type Board } from "@/entities/board"; import { AttachmentApi, type Attachment } from "@/entities/attachment"; import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post"; import { FormControl, FormField, FormItem } from "@/shared/ui/form"; @@ -38,7 +38,7 @@ function PostForm({ form, handleSave, }: PostFormProps) { - const atflCd = form.watch("atflCd" as FieldPath); + const atflCd = form.getValues("atflCd" as FieldPath); const { userInfo } = useUserStore((state) => state); const [storedBbsInfo] = useLocalStorage("bbs-info", ""); @@ -125,7 +125,7 @@ function PostForm({ @@ -146,7 +146,7 @@ function PostForm({ @@ -169,7 +169,7 @@ function PostForm({ @@ -191,7 +191,7 @@ function PostForm({