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, keywordType }); - }} - > - - - - - 제목 - 제목+내용 - 작성자 - - - - setPostParams({ ...postParams, keyword: e.target.value }) - } - className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" - /> - - 조회 - - - - - - - 공지사항 목록 - - {pageMenuAuthority?.regAuthrtYn === "Y" && ( - router.push(`${pathname}/register`)}> - 등록 - - )} - { - table.setPageSize(parseInt(value)); - }} - > - - - - - {[10, 20, 30, 40, 50].map((pageSize) => ( - - {pageSize}개씩 보기 - - ))} - - - - - - - - - - - ); -} - -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({ ({ @@ -229,13 +229,13 @@ function PostForm({ 팝업여부 - } - render={({ field }) => ( - - - + + } + render={({ field }) => ( + + ({ 사용안함 - - - - - )} - /> + + + )} + /> + } + render={({ field }) => ( + + + ) === + "N" + } + handlePeriodValueChange={(periodCode) => { + field.onChange([ + { + ...(form.getValues().popupYnEtc?.[0] ?? {}), + bbsPstgTypeCd: BOARD_POSTING_CODE.팝업게시, + pstgPrdSeCd: periodCode, + }, + ]); + }} + handlePostingDateChange={(date) => { + field.onChange([ + { + ...(form.getValues().popupYnEtc?.[0] ?? {}), + ...date, + }, + ]); + }} + /> + + + )} + /> + )} @@ -263,13 +299,13 @@ function PostForm({ 상단고정 - } - render={({ field }) => ( - - - + + } + render={({ field }) => ( + + ({ 사용안함 - - - - - )} - /> + + + )} + /> + } + render={({ field }) => ( + + + ) === + "N" + } + handlePeriodValueChange={(periodCode) => { + field.onChange([ + { + ...(form.getValues().upndFixYnEtc?.[0] ?? + {}), + bbsPstgTypeCd: BOARD_POSTING_CODE.상단게시, + pstgPrdSeCd: periodCode, + }, + ]); + }} + handlePostingDateChange={(date) => { + field.onChange([ + { + ...(form.getValues().upndFixYnEtc?.[0] ?? + {}), + ...date, + }, + ]); + }} + /> + + + )} + /> + )} @@ -297,13 +371,13 @@ function PostForm({ 게시기간 - } - render={({ field }) => ( - - - + + } + render={({ field }) => ( + + ({ 사용안함 - - - - - )} - /> + + + )} + /> + } + render={({ field }) => ( + + + ) === + "N" + } + handlePeriodValueChange={(periodCode) => { + field.onChange([ + { + ...(form.getValues().prdPstgYnEtc?.[0] ?? + {}), + bbsPstgTypeCd: BOARD_POSTING_CODE.기간게시, + pstgPrdSeCd: periodCode, + }, + ]); + }} + handlePostingDateChange={(date) => { + field.onChange([ + { + ...(form.getValues().prdPstgYnEtc?.[0] ?? + {}), + ...date, + }, + ]); + }} + /> + + + )} + /> + )} @@ -382,10 +494,36 @@ function PostForm({ ); } -const PeriodSelector = ({ disabled }: { disabled: boolean }) => { - const [periodCode, setPeriodCode] = useState(null); - const handlePeriodCodeChange = (value: string) => { - setPeriodCode(parseInt(value)); +const PeriodSelector = ({ + periodCode, + disabled, + handlePeriodValueChange, + handlePostingDateChange, +}: { + periodCode: string | null; + disabled: boolean; + handlePeriodValueChange: (periodCode: string) => void; + handlePostingDateChange: (date?: { + pstgBgngDt: string | undefined; + pstgEndDt: string | undefined; + }) => void; +}) => { + const handlePeriodCodeChange = (periodCode: string) => { + handlePeriodValueChange(periodCode); + handlePostingDateChange({ + pstgBgngDt: undefined, + pstgEndDt: undefined, + }); + }; + + const onDateChange = (date?: { + from: Date | undefined; + to?: Date | undefined; + }) => { + handlePostingDateChange({ + pstgBgngDt: dayjs(date?.from).format("YYYY-MM-DD 00:00:00"), + pstgEndDt: dayjs(date?.to).format("YYYY-MM-DD 23:59:59"), + }); }; const postingPeriod = (periodCode: number | null) => { @@ -400,16 +538,19 @@ const PeriodSelector = ({ disabled }: { disabled: boolean }) => { {!!periodCode && ( - 게시 기간: {postingPeriod(periodCode)} + 게시 기간: {postingPeriod(parseInt(periodCode))} )} - {periodCode === 0 && } + {periodCode === "0" && ( + + )} ); }; diff --git a/apps/common/features/src/widgets/post/ui/post-page.tsx b/apps/common/features/src/widgets/post/ui/post-page.tsx new file mode 100644 index 000000000..8445c0b84 --- /dev/null +++ b/apps/common/features/src/widgets/post/ui/post-page.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useLocalStorage } from "@frontend-opensource/use-react-hooks"; +import { useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + useReactTable, + type PaginationState, +} 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 type { Board } from "@/entities/board"; +import { ENDPOINT } from "@/shared/config"; +import { useMenuStore } from "@/shared/lib"; +import { columns } from "../model/post-table-columns"; + +function PostPage({ bbsId }: { bbsId: string }) { + const router = useRouter(); + const pathname = usePathname(); + const [storedBbsInfo] = useLocalStorage("bbs-info", ""); + const { pageMenuAuthority } = useMenuStore((state) => state); + + const [bbsInfo, setBbsInfo] = useState(null); + const [postParams, setPostParams] = useState({ + keywordType: "title", + keyword: "", + }); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const { data: upndFixPostList } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo], + queryFn: () => + PostApi.getPostList(bbsId, { + ...postParams, + upndFixYn: bbsInfo?.upndFixUseYn, + }), + select: (data) => { + const newContent = data.content.map((post) => ({ + ...post, + isUpndFixPost: post.upndFixYn === "Y", + })); + return { + ...data, + content: newContent, + }; + }, + enabled: bbsInfo?.upndFixUseYn === "Y", + }); + + 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: [...(upndFixPostList?.content ?? []), ...(postList?.content ?? [])], + columns, + state: { pagination }, + rowCount: postList?.totalElements ?? 0, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + const handleRowClick = (row: Post) => { + router.push(`${pathname}/${row.pstNo}`); + }; + + useEffect(() => { + if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo)); + }, [storedBbsInfo]); + + useEffect(() => { + refetch(); + }, [pagination]); + + return ( + + + + 검색 + { + setPostParams({ ...postParams, keywordType }); + }} + > + + + + + 제목 + 제목+내용 + 작성자 + + + + setPostParams({ ...postParams, keyword: e.target.value }) + } + className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" + /> + + 조회 + + + + + + + 콘텐츠 목록 + + {pageMenuAuthority?.regAuthrtYn === "Y" && ( + router.push(`${pathname}/register`)}> + 등록 + + )} + { + table.setPageSize(parseInt(value)); + }} + > + + + + + {[10, 20, 30, 40, 50].map((pageSize) => ( + + {pageSize}개씩 보기 + + ))} + + + + + + + + + + + ); +} + +export { PostPage }; diff --git a/apps/common/features/src/widgets/post/ui/post.tsx b/apps/common/features/src/widgets/post/ui/post.tsx index 3f3d187e2..920eab753 100644 --- a/apps/common/features/src/widgets/post/ui/post.tsx +++ b/apps/common/features/src/widgets/post/ui/post.tsx @@ -1,68 +1,63 @@ "use client"; import dayjs from "dayjs"; -import type { AxiosResponse } from "axios"; -import { useEffect, useState, useTransition } from "react"; +import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Button, Skeleton } from "@common/components"; -import { PostApi } from "@/entities/post"; import { AttachmentApi } from "@/entities/attachment"; +import { PostApi } from "@/entities/post"; import { ENDPOINT } from "@/shared/config"; import { PostAction } from "./post-action"; function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { - const [isPending, startTransition] = useTransition(); const [filename, setFilename] = useState(""); - const [downloadResponse, setDownloadResponse] = - useState(null); const { data: postData } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], queryFn: () => PostApi.getPostInfo(bbsId, pstNo), }); - useEffect(() => { - if (!postData?.atflCd) return; - startTransition(async () => { - try { - let filename = ""; - const response = await AttachmentApi.downloadAttachmentByAtflCd( - postData.atflCd, - ); - setDownloadResponse(response); + const { data: downloadResponse, isFetching } = useQuery({ + queryKey: [ENDPOINT.ATTACH_SERVICE.DOWNLOAD, postData?.atflCd], + queryFn: () => + AttachmentApi.downloadAttachmentByAtflCd(postData?.atflCd as string), + enabled: !!postData?.atflCd, + }); - const contentDisposition = response.headers["content-disposition"]; - if (contentDisposition) { - const filenameMatch = contentDisposition.match( - /filename\*=UTF-8''([^;]+)/, - ); - if (filenameMatch && filenameMatch[1]) { - filename = decodeURIComponent(filenameMatch[1]); - } + 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(""); } - }); - }, [postData?.atflCd]); + setFilename(filename); + } catch { + setFilename(""); + } + }, [downloadResponse]); const handleDownload = async () => { - if (isPending || !downloadResponse) return; + if (!downloadResponse) return; try { - startTransition(() => { - const blob = new Blob([downloadResponse.data]); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); + 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(); + link.href = url; + link.download = filename || "download"; + document.body.appendChild(link); + link.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(link); - }); + window.URL.revokeObjectURL(url); + document.body.removeChild(link); } catch (error) { console.error(error); } @@ -93,7 +88,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { 첨부파일 - {isPending ? ( + {isFetching ? ( ) : ( filename && ( @@ -102,7 +97,6 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { color="white" className="flex w-fit items-center gap-2" onClick={handleDownload} - disabled={isPending} > {filename}