feat: 첨부파일 다운로드 작업
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
@@ -54,6 +55,16 @@ export class AttachmentApiService implements AttachmentService {
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public async downloadAttachmentByAtflCd(
|
||||
atflCd: string,
|
||||
): Promise<AxiosResponse> {
|
||||
const response = await client.get(
|
||||
`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`,
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export const AttachmentApi = new AttachmentApiService();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
import type { PageableList, PageableParams } from "@/shared/model";
|
||||
|
||||
export interface AttachmentParams extends PageableParams {
|
||||
@@ -40,4 +41,5 @@ export interface AttachmentService {
|
||||
deleteAttachmentByAtflCd: (atflCd: string) => Promise<void>;
|
||||
getImageUrl: (atflId: string) => Promise<string>;
|
||||
getImageUrlByAtflCd: (atflCd: string) => Promise<string>;
|
||||
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import type { Post } from "@/entities/post";
|
||||
import { Badge, Button } from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import type { Post } from "@/entities/post";
|
||||
|
||||
export const columns: ColumnDef<Post>[] = [
|
||||
{
|
||||
@@ -51,9 +52,39 @@ export const columns: ColumnDef<Post>[] = [
|
||||
];
|
||||
|
||||
const AttachmentButton = ({ atflCd }: { atflCd: string }) => {
|
||||
if (!atflCd) return null;
|
||||
const handleDownload = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
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 (
|
||||
<Button size="icon" color="white">
|
||||
<Button size="icon" color="white" onClick={handleDownload}>
|
||||
<i className="diveicon di-download" />
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ export const ENDPOINT = {
|
||||
EDITOR_UPLOAD: "/attach-service/api/v1/upload/editor",
|
||||
IMAGES: "/attach-service/api/v1/images",
|
||||
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
|
||||
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
|
||||
},
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
|
||||
@@ -1,17 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { useEffect, useState, useTransition } 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 { 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<AxiosResponse | null>(null);
|
||||
|
||||
const { data: postData } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS],
|
||||
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 contentDisposition = response.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]);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (isPending || !downloadResponse) return;
|
||||
try {
|
||||
startTransition(() => {
|
||||
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();
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PostAction bbsId={bbsId} pstNo={pstNo}>
|
||||
<div className="card card-border !p-8">
|
||||
@@ -36,7 +92,23 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
/>
|
||||
<hr className="mt-8" />
|
||||
<div className="flex items-center gap-4 p-8">
|
||||
<span>첨부파일</span>
|
||||
<span className="font-bold">첨부파일</span>
|
||||
{isPending ? (
|
||||
<Skeleton className="h-[2.5rem] w-[20rem]" />
|
||||
) : (
|
||||
filename && (
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
className="flex w-fit items-center gap-2"
|
||||
onClick={handleDownload}
|
||||
disabled={isPending}
|
||||
>
|
||||
<i className="diveicon di-download" />
|
||||
<span>{filename}</span>
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PostAction>
|
||||
|
||||
Reference in New Issue
Block a user