From b202b4a99d5185f1e2137058a76bd4a1cde8f7cc Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 12 May 2025 17:02:25 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20GeneralFileUploader=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/post/ui/general-file-uploader.tsx | 129 ++++++++++++++++++ .../src/widgets/post/ui/post-form.tsx | 118 ++-------------- 2 files changed, 140 insertions(+), 107 deletions(-) create mode 100644 apps/common/features/src/widgets/post/ui/general-file-uploader.tsx diff --git a/apps/common/features/src/widgets/post/ui/general-file-uploader.tsx b/apps/common/features/src/widgets/post/ui/general-file-uploader.tsx new file mode 100644 index 000000000..b28614587 --- /dev/null +++ b/apps/common/features/src/widgets/post/ui/general-file-uploader.tsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react"; +import type { FieldPath, UseFormReturn } from "react-hook-form"; +import type { HttpResponse, Upload } from "tus-js-client"; +import { useLocalStorage } from "@frontend-opensource/use-react-hooks"; +import { Button, FileUploader } from "@common/components"; +import type { Board } from "@/entities/board"; +import { handleTusUpload } from "@/entities/tus"; +import { type Attachment } from "@/entities/attachment"; +import type { PostFormData, PostUpdateFormData } from "@/entities/post"; +import { setCookie, useAlertStore } from "@/shared/lib"; + +function GeneralFileUploader({ + form, + attachmentData, + deleteAttachmentIds, + setDeleteAttachmentIds, +}: { + form: UseFormReturn; + attachmentData?: Attachment[]; + deleteAttachmentIds: string[]; + setDeleteAttachmentIds: (deleteAttachmentIds: string[]) => void; +}) { + const [storedBbsInfo] = useLocalStorage("bbs-info", ""); + const { setMessage: alert } = useAlertStore((state) => state); + + const [bbsInfo, setBbsInfo] = useState(null); + const [attachments, setAttachments] = useState>( + {}, + ); + + const handleTusResponse = (res: HttpResponse, upload: Upload) => { + const fileInfo = res.getHeader("X-File-Info"); + if ( + typeof fileInfo !== "string" || + fileInfo === undefined || + fileInfo === null + ) + return; + + const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; + if (atflCd) form.setValue("atflCd" as FieldPath, atflCd); + if (atflId) { + setAttachments((prev) => ({ + ...prev, + [atflId]: { + atflCd, + atflId, + orgnlFileNm: (upload.file as File).name, + atflFlieSz: (upload.file as File).size, + atflType: (upload.file as File).type, + }, + })); + } + }; + + const onUpload = (e: React.ChangeEvent) => { + const atflCd = form.getValues("atflCd" as FieldPath); + if (atflCd) setCookie("atfl-cd-key", atflCd as string); + + const files = e.target.files; + if (!files) return; + if (bbsInfo?.atflMaxCnt && files.length > bbsInfo.atflMaxCnt) { + alert("첨부파일 최대 개수를 초과했습니다."); + return; + } + + for (const file of files) { + const fileSize = Math.round(file.size / 1024); // Byte -> KB + if (bbsInfo?.atflMaxFileSz && fileSize > bbsInfo.atflMaxFileSz) { + alert("첨부파일 최대 크기를 초과했습니다."); + return; + } + handleTusUpload(file, { + handleResponse: handleTusResponse, + }); + } + }; + + useEffect(() => { + const mapper = (data: Attachment[]) => + data.reduce( + (acc, curr) => { + acc[curr.atflId] = curr; + return acc; + }, + {} as Record, + ); + if (attachmentData) setAttachments(mapper(attachmentData)); + }, [attachmentData]); + + useEffect(() => { + if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo)); + }, [storedBbsInfo]); + + return ( + + {Object.entries(attachments).map(([atflId, attachment]) => { + if (deleteAttachmentIds.includes(atflId)) return null; + return ( +
+ {attachment.orgnlFileNm} + +
+ ); + })} + + } + /> + ); +} + +export { GeneralFileUploader }; 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 8192b0a51..895309ed5 100644 --- a/apps/common/features/src/widgets/post/ui/post-form.tsx +++ b/apps/common/features/src/widgets/post/ui/post-form.tsx @@ -1,7 +1,6 @@ import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import type { HttpResponse, Upload } from "tus-js-client"; import type { UseFormReturn, FieldPath } from "react-hook-form"; import { useConfirm, @@ -18,17 +17,16 @@ import { TableHead, TableRow, Textarea, - FileUploader, Button, } from "@common/components"; -import { handleTusUpload } from "@/entities/tus"; import { BOARD_POSTING_CODE, type Board } from "@/entities/board"; -import { AttachmentApi, type Attachment } from "@/entities/attachment"; +import { AttachmentApi } from "@/entities/attachment"; import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post"; import { ENDPOINT } from "@/shared/config"; -import { useUserStore, useAlertStore, setCookie } from "@/shared/lib"; +import { useUserStore, setCookie } from "@/shared/lib"; import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; import { PostingPeriodSelector } from "./posting-period-selector"; +import { GeneralFileUploader } from "./general-file-uploader"; type PostFormProps = { form: UseFormReturn; @@ -45,14 +43,10 @@ function PostForm({ const { confirm } = useConfirm(); const { userInfo } = useUserStore((state) => state); - const { setMessage: alert } = useAlertStore((state) => state); const [storedBbsInfo] = useLocalStorage("bbs-info", ""); const [bbsInfo, setBbsInfo] = useState(null); - const [attachments, setAttachments] = useState>( - {}, - ); - const [deleteAttachments, setDeleteAttachments] = useState([]); + const [deleteAttachmentIds, setDeleteAttachmentIds] = useState([]); const { data: attachmentData } = useQuery({ queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, atflCd], @@ -60,77 +54,17 @@ function PostForm({ enabled: !!atflCd, }); - const handleTusResponse = (res: HttpResponse, upload: Upload) => { - const fileInfo = res.getHeader("X-File-Info"); - if ( - typeof fileInfo !== "string" || - fileInfo === undefined || - fileInfo === null - ) - return; - - const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; - if (atflCd) form.setValue("atflCd" as FieldPath, atflCd); - if (atflId) { - setAttachments((prev) => ({ - ...prev, - [atflId]: { - atflCd, - atflId, - orgnlFileNm: (upload.file as File).name, - atflFlieSz: (upload.file as File).size, - atflType: (upload.file as File).type, - }, - })); - } - }; - const onSubmit = async (data: T) => { if (await confirm("저장하시겠습니까?")) { - if (deleteAttachments.length > 0) { + if (deleteAttachmentIds.length > 0) { await Promise.all( - deleteAttachments.map(AttachmentApi.deleteAttachmentByAtflId), + deleteAttachmentIds.map(AttachmentApi.deleteAttachmentByAtflId), ); } await handleSave(data); } }; - const handleUpload = (e: React.ChangeEvent) => { - const atflCd = form.getValues("atflCd" as FieldPath); - if (atflCd) setCookie("atfl-cd-key", atflCd as string); - - const files = e.target.files; - if (!files) return; - if (bbsInfo?.atflMaxCnt && files.length > bbsInfo.atflMaxCnt) { - alert("첨부파일 최대 개수를 초과했습니다."); - return; - } - - for (const file of files) { - const fileSize = Math.round(file.size / 1024); // Byte -> KB - if (bbsInfo?.atflMaxFileSz && fileSize > bbsInfo.atflMaxFileSz) { - alert("첨부파일 최대 크기를 초과했습니다."); - return; - } - handleTusUpload(file, { - handleResponse: handleTusResponse, - }); - } - }; - - useEffect(() => { - const mapper = (data: Attachment[]) => - data.reduce( - (acc, curr) => { - acc[curr.atflId] = curr; - return acc; - }, - {} as Record, - ); - if (attachmentData) setAttachments(mapper(attachmentData)); - }, [attachmentData]); - useEffect(() => { if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo)); }, [storedBbsInfo]); @@ -485,41 +419,11 @@ function PostForm({ 첨부파일 - - {Object.entries(attachments).map( - ([atflId, attachment]) => { - if (deleteAttachments.includes(atflId)) - return null; - return ( -
- {attachment.orgnlFileNm} - -
- ); - }, - )} - - } +