From 9816a9c46a382df9a4cfe6735d245230f84f66e7 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 29 Jun 2026 13:49:33 +0900 Subject: [PATCH] =?UTF-8?q?[as]=20feat:=20=EC=B2=A8=EB=B6=80=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/entities/attach/api/attach-service.ts | 22 +++++++++++ apps/as/src/entities/attach/api/queries.ts | 39 +++++++++++++++++++ apps/as/src/entities/attach/index.ts | 3 ++ .../lib/parse-filename-from-response.ts | 11 ++++++ .../entities/attach/model/attach-interface.ts | 22 +++++++++++ .../instl-sncn-old/ui/cnfm-regist-page.tsx | 4 +- .../ui/sections/attachments-form-section.tsx | 24 ++++++++---- .../ui/sections/attachments-section.tsx | 25 ++++-------- apps/as/src/shared/config/api.ts | 1 + apps/as/src/shared/lib/attach.ts | 17 -------- 10 files changed, 125 insertions(+), 43 deletions(-) create mode 100644 apps/as/src/entities/attach/api/attach-service.ts create mode 100644 apps/as/src/entities/attach/api/queries.ts create mode 100644 apps/as/src/entities/attach/index.ts create mode 100644 apps/as/src/entities/attach/lib/parse-filename-from-response.ts create mode 100644 apps/as/src/entities/attach/model/attach-interface.ts delete mode 100644 apps/as/src/shared/lib/attach.ts diff --git a/apps/as/src/entities/attach/api/attach-service.ts b/apps/as/src/entities/attach/api/attach-service.ts new file mode 100644 index 000000000..0af0a916d --- /dev/null +++ b/apps/as/src/entities/attach/api/attach-service.ts @@ -0,0 +1,22 @@ +import { AxiosResponse } from "axios"; + +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { AttachService } from "../model/attach-interface"; + +async function deleteAttachmentByAtflId(atflId: string): Promise { + if (!atflId) return; + await client.delete(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`); +} + +async function downloadAttachmentByAtflCd(atflCd: string): Promise { + return await client.get(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`, { + responseType: "blob", + }); +} + +export const attachService = { + deleteAttachmentByAtflId, + downloadAttachmentByAtflCd, +} satisfies AttachService; diff --git a/apps/as/src/entities/attach/api/queries.ts b/apps/as/src/entities/attach/api/queries.ts new file mode 100644 index 000000000..ed9efefbf --- /dev/null +++ b/apps/as/src/entities/attach/api/queries.ts @@ -0,0 +1,39 @@ +import { useMutation } from "@tanstack/react-query"; + +import { ENDPOINT } from "@/shared/config"; + +import { parseFilenameFromResponse } from "../lib/parse-filename-from-response"; + +import { attachService } from "./attach-service"; + +const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const; +const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const; + +export function useDeleteAttachment() { + return useMutation({ + mutationKey: [...attachBaseKey, "delete"], + mutationFn: (atflId: string) => attachService.deleteAttachmentByAtflId(atflId), + }); +} + +export function useDownloadAttachmentByAtflCd() { + return useMutation({ + mutationKey: [...attachDownloadBaseKey, "download"], + mutationFn: async (atflCd: string) => { + const response = await attachService.downloadAttachmentByAtflCd(atflCd); + const filename = parseFilenameFromResponse(response); + + 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); + }, + }); +} diff --git a/apps/as/src/entities/attach/index.ts b/apps/as/src/entities/attach/index.ts new file mode 100644 index 000000000..37258e6e5 --- /dev/null +++ b/apps/as/src/entities/attach/index.ts @@ -0,0 +1,3 @@ +export { attachService } from "./api/attach-service"; +export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries"; +export type { Attach, AttachService } from "./model/attach-interface"; diff --git a/apps/as/src/entities/attach/lib/parse-filename-from-response.ts b/apps/as/src/entities/attach/lib/parse-filename-from-response.ts new file mode 100644 index 000000000..cd8a5aa3d --- /dev/null +++ b/apps/as/src/entities/attach/lib/parse-filename-from-response.ts @@ -0,0 +1,11 @@ +import type { AxiosResponse } from "axios"; + +export function parseFilenameFromResponse(response: AxiosResponse): string { + const contentDisposition = response.headers["content-disposition"]; + if (!contentDisposition) return "download"; + + const filenameMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/); + if (!filenameMatch?.[1]) return "download"; + + return decodeURIComponent(filenameMatch[1]); +} diff --git a/apps/as/src/entities/attach/model/attach-interface.ts b/apps/as/src/entities/attach/model/attach-interface.ts new file mode 100644 index 000000000..d6b0a08d2 --- /dev/null +++ b/apps/as/src/entities/attach/model/attach-interface.ts @@ -0,0 +1,22 @@ +import { AxiosResponse } from "axios"; + +/** 백엔드 AtflMngResponseDto */ +export interface Attach { + atflCd: string; + atflSn: number; + atflId: string; + physFileNm: string; + orgnlFileNm: string; + atflFlieSz: number; + atflType: string; + dwnldNocs: number; + delYn: string; + lnkgDmnId: string; + lnkgDmnNm: string; + regDt: string; +} + +export interface AttachService { + deleteAttachmentByAtflId: (atflId: string) => Promise; + downloadAttachmentByAtflCd: (atflCd: string) => Promise; +} diff --git a/apps/as/src/pages/instl-sncn-old/ui/cnfm-regist-page.tsx b/apps/as/src/pages/instl-sncn-old/ui/cnfm-regist-page.tsx index 4c4844481..7b545ec06 100644 --- a/apps/as/src/pages/instl-sncn-old/ui/cnfm-regist-page.tsx +++ b/apps/as/src/pages/instl-sncn-old/ui/cnfm-regist-page.tsx @@ -10,10 +10,10 @@ import { type HttpResponse, Upload } from "tus-js-client"; import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT, PROXY_HOST } from "@/shared/config"; import { getCookie, setCookie, useAlertStore, useUserStore } from "@/shared/lib"; -import { deleteAttachment } from "@/shared/lib/attach"; import { formatFileSize } from "@/shared/lib/file-utils"; import type { AcntgSeMngTreeDto } from "@/entities/acntg"; import { acntgSeMngService } from "@/entities/acntg"; +import { attachService } from "@/entities/attach"; import type { CklstTreeNode } from "@/entities/cklst"; import { cklstMngService } from "@/entities/cklst"; import type { CnstpSeMngTreeDto } from "@/entities/cnstp"; @@ -509,7 +509,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) { const handleDeleteEtcFile = async (fileId: string, atflId: string) => { try { if (atflId) { - await deleteAttachment(atflId); + await attachService.deleteAttachmentByAtflId(atflId); } } catch (error) { console.error("기타 첨부파일 삭제 실패", error); diff --git a/apps/as/src/pages/instl-sncn/ui/sections/attachments-form-section.tsx b/apps/as/src/pages/instl-sncn/ui/sections/attachments-form-section.tsx index 04928b1fb..3befa9b29 100644 --- a/apps/as/src/pages/instl-sncn/ui/sections/attachments-form-section.tsx +++ b/apps/as/src/pages/instl-sncn/ui/sections/attachments-form-section.tsx @@ -7,7 +7,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { ENDPOINT } from "@/shared/config"; import { getDefaultFileKey, useTus } from "@/shared/lib"; -import { deleteAttachment } from "@/shared/lib/attach"; +import { attachService } from "@/entities/attach"; import { useEsntlAtchFileMngTree } from "@/entities/esntl"; import type { AtflMngClientResponseDto, @@ -242,7 +242,7 @@ function AttachmentsFormSection({ if (fileToRemove.atflId) { try { - await deleteAttachment(fileToRemove.atflId); + await attachService.deleteAttachmentByAtflId(fileToRemove.atflId); await invalidateAttachmentQueries(); } catch (error) { console.error("기타 첨부 파일 삭제 실패:", error); @@ -267,14 +267,16 @@ function AttachmentsFormSection({ if (atflId) { try { - await deleteAttachment(atflId); + await attachService.deleteAttachmentByAtflId(atflId); await invalidateAttachmentQueries(); } catch (error) { console.error("파일 삭제 실패:", error); } } - const updatedList = (uploadedFiles[esntlAtchFileId] ?? []).filter((_, index) => index !== fileIndex); + const updatedList = (uploadedFiles[esntlAtchFileId] ?? []).filter( + (_, index) => index !== fileIndex, + ); const isSlotEmpty = updatedList.length === 0; setUploadedFiles((prev) => { @@ -312,7 +314,10 @@ function AttachmentsFormSection({ setUploadedFiles((prev) => ({ ...prev, - [esntlAtchFileId]: [...(prev[esntlAtchFileId] ?? []), { file, fileName: file.name, fileKey }], + [esntlAtchFileId]: [ + ...(prev[esntlAtchFileId] ?? []), + { file, fileName: file.name, fileKey }, + ], })); uploadMappingRef.current.set(fileKey, { esntlAtchFileId, file }); @@ -391,7 +396,9 @@ function AttachmentsFormSection({ return (
- {item.file?.name ?? item.fileName} + + {item.file?.name ?? item.fileName} +