refactor: GeneralFileUploader 컴포넌트 분리
This commit is contained in:
@@ -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<T extends PostFormData | PostUpdateFormData>({
|
||||
form,
|
||||
attachmentData,
|
||||
deleteAttachmentIds,
|
||||
setDeleteAttachmentIds,
|
||||
}: {
|
||||
form: UseFormReturn<T>;
|
||||
attachmentData?: Attachment[];
|
||||
deleteAttachmentIds: string[];
|
||||
setDeleteAttachmentIds: (deleteAttachmentIds: string[]) => void;
|
||||
}) {
|
||||
const [storedBbsInfo] = useLocalStorage("bbs-info", "");
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const [bbsInfo, setBbsInfo] = useState<Board | null>(null);
|
||||
const [attachments, setAttachments] = useState<Record<string, Attachment>>(
|
||||
{},
|
||||
);
|
||||
|
||||
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<T>, 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<HTMLInputElement>) => {
|
||||
const atflCd = form.getValues("atflCd" as FieldPath<T>);
|
||||
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<string, Attachment>,
|
||||
);
|
||||
if (attachmentData) setAttachments(mapper(attachmentData));
|
||||
}, [attachmentData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo));
|
||||
}, [storedBbsInfo]);
|
||||
|
||||
return (
|
||||
<FileUploader
|
||||
id="atflCd"
|
||||
multiple
|
||||
className="flex !h-auto max-w-full flex-row-reverse gap-4 [&>label]:h-[3.2rem]"
|
||||
onUpload={onUpload}
|
||||
customPreview={
|
||||
<div className="flex w-full gap-4">
|
||||
{Object.entries(attachments).map(([atflId, attachment]) => {
|
||||
if (deleteAttachmentIds.includes(atflId)) return null;
|
||||
return (
|
||||
<div
|
||||
key={atflId}
|
||||
className="flex h-[3.2rem] items-center gap-2 rounded-sm border border-border bg-background px-3 py-2 text-foreground"
|
||||
>
|
||||
<span>{attachment.orgnlFileNm}</span>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex size-7 items-center justify-center rounded-full p-0"
|
||||
onClick={() =>
|
||||
setDeleteAttachmentIds([...deleteAttachmentIds, atflId])
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-x text-xs" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { GeneralFileUploader };
|
||||
@@ -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<T extends PostFormData | PostUpdateFormData> = {
|
||||
form: UseFormReturn<T>;
|
||||
@@ -45,14 +43,10 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [storedBbsInfo] = useLocalStorage("bbs-info", "");
|
||||
|
||||
const [bbsInfo, setBbsInfo] = useState<Board | null>(null);
|
||||
const [attachments, setAttachments] = useState<Record<string, Attachment>>(
|
||||
{},
|
||||
);
|
||||
const [deleteAttachments, setDeleteAttachments] = useState<string[]>([]);
|
||||
const [deleteAttachmentIds, setDeleteAttachmentIds] = useState<string[]>([]);
|
||||
|
||||
const { data: attachmentData } = useQuery({
|
||||
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, atflCd],
|
||||
@@ -60,77 +54,17 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
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<T>, 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<HTMLInputElement>) => {
|
||||
const atflCd = form.getValues("atflCd" as FieldPath<T>);
|
||||
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<string, Attachment>,
|
||||
);
|
||||
if (attachmentData) setAttachments(mapper(attachmentData));
|
||||
}, [attachmentData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo));
|
||||
}, [storedBbsInfo]);
|
||||
@@ -485,41 +419,11 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<FileUploader
|
||||
id="atflCd"
|
||||
multiple
|
||||
className="flex !h-auto max-w-full flex-row-reverse gap-4 [&>label]:h-[3.2rem]"
|
||||
onUpload={handleUpload}
|
||||
customPreview={
|
||||
<div className="flex w-full gap-4">
|
||||
{Object.entries(attachments).map(
|
||||
([atflId, attachment]) => {
|
||||
if (deleteAttachments.includes(atflId))
|
||||
return null;
|
||||
return (
|
||||
<div
|
||||
key={atflId}
|
||||
className="flex h-[3.2rem] items-center gap-2 rounded-sm border border-border bg-background px-3 py-2 text-foreground"
|
||||
>
|
||||
<span>{attachment.orgnlFileNm}</span>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex size-7 items-center justify-center rounded-full p-0"
|
||||
onClick={() =>
|
||||
setDeleteAttachments((prev) => [
|
||||
...prev,
|
||||
atflId,
|
||||
])
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-x text-xs" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
<GeneralFileUploader
|
||||
form={form}
|
||||
attachmentData={attachmentData}
|
||||
deleteAttachmentIds={deleteAttachmentIds}
|
||||
setDeleteAttachmentIds={setDeleteAttachmentIds}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user