123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import type { FieldPath, UseFormReturn } from "react-hook-form";
|
|
import type { HttpResponse, Upload } from "tus-js-client";
|
|
import { Button, FileUploader } from "@common/components";
|
|
import { handleTusUpload } from "@/entities/tus";
|
|
import { type Attachment } from "@/entities/attachment";
|
|
import type { PostFormData, PostUpdateFormData } from "@/entities/post";
|
|
import { setCookie, useAlertStore, useMenuStore } 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 { setMessage: alert } = useAlertStore((state) => state);
|
|
const { bbsInfo } = useMenuStore((state) => state);
|
|
|
|
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]);
|
|
|
|
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 };
|