Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -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<void> {
|
||||
if (!atflId) return;
|
||||
await client.delete(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`);
|
||||
}
|
||||
|
||||
async function downloadAttachmentByAtflCd(atflCd: string): Promise<AxiosResponse> {
|
||||
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`, {
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
export const attachService = {
|
||||
deleteAttachmentByAtflId,
|
||||
downloadAttachmentByAtflCd,
|
||||
} satisfies AttachService;
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { attachService } from "./api/attach-service";
|
||||
export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries";
|
||||
export type { Attach, AttachService } from "./model/attach-interface";
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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<void>;
|
||||
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
<div key={item.fileKey} className="flex items-center gap-2">
|
||||
<span className="text-xl">{item.file?.name ?? item.fileName}</span>
|
||||
<span className="text-xl">
|
||||
{item.file?.name ?? item.fileName}
|
||||
</span>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
@@ -413,7 +420,10 @@ function AttachmentsFormSection({
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
void handleEssentialFileUpload(file.esntlAtchFileId, e.target.files);
|
||||
void handleEssentialFileUpload(
|
||||
file.esntlAtchFileId,
|
||||
e.target.files,
|
||||
);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { formatFileSize } from "@/shared/lib/file-utils";
|
||||
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import type {
|
||||
AtflMngClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "@/entities/instl-sncn";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
import { SectionCollapsible } from "../components/section-collapsible";
|
||||
|
||||
@@ -22,11 +15,9 @@ interface AttachmentsSectionProps {
|
||||
etcFileList: AtflMngClientResponseDto[];
|
||||
}
|
||||
|
||||
function getDownloadUrl(atflCd: string) {
|
||||
return `${PROXY_HOST}/attach-service/api/v1/atflcd/download/${atflCd}`;
|
||||
}
|
||||
|
||||
function AttachmentsSection({ esntlFileList, etcFileList }: AttachmentsSectionProps) {
|
||||
const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionCollapsible title="필수 첨부파일">
|
||||
@@ -58,8 +49,8 @@ function AttachmentsSection({ esntlFileList, etcFileList }: AttachmentsSectionPr
|
||||
<TableCell className="text-left">
|
||||
{file.atflCd ? (
|
||||
<a
|
||||
className="text-blue-600 hover:underline"
|
||||
href={getDownloadUrl(file.atflCd)}
|
||||
className="cursor-pointer text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(file.atflCd ?? "")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@@ -100,8 +91,8 @@ function AttachmentsSection({ esntlFileList, etcFileList }: AttachmentsSectionPr
|
||||
<TableCell className="text-left">
|
||||
{file.atflCd ? (
|
||||
<a
|
||||
className="text-blue-600 hover:underline"
|
||||
href={getDownloadUrl(file.atflCd)}
|
||||
className="cursor-pointer text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(file.atflCd)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ export const ENDPOINT = {
|
||||
ATTACH_SERVICE: {
|
||||
TUS_UPLOAD: "/attach-service/api/v1/tus/upload",
|
||||
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
|
||||
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
|
||||
},
|
||||
AS_SERVICE: {
|
||||
ACNTG_SE: "/as-service/api/v1/acntg-se",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { getCookie } from "@/shared/lib";
|
||||
import { CLAIM_NAME, AUTH_USER_ID } from "@/shared/config/env";
|
||||
import axios from "axios";
|
||||
import { PROXY_HOST, ENDPOINT } from "@/shared/config";
|
||||
|
||||
/**
|
||||
* Common helper to delete attachment using cookies for auth headers.
|
||||
*/
|
||||
export async function deleteAttachment(atflId: string): Promise<void> {
|
||||
if (!atflId) return;
|
||||
const headers: Record<string, string> = {
|
||||
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
|
||||
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
|
||||
};
|
||||
const url = `${PROXY_HOST}${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`;
|
||||
await axios.delete(url, { headers });
|
||||
}
|
||||
Reference in New Issue
Block a user