ERP-node/frontend/lib/api/file.ts

271 lines
7.7 KiB
TypeScript

import { apiClient } from "./client";
export interface FileInfo {
id: string;
name: string;
size: number;
type: string;
extension: string;
uploadedAt: string;
lastModified: string;
serverPath?: string;
serverFilename?: string;
}
export interface FileUploadResponse {
success: boolean;
message: string;
files: FileInfo[];
data?: FileInfo[];
}
export interface FileDownloadParams {
fileId: string;
serverFilename: string;
originalName: string;
}
/**
* 파일 업로드
*/
export const uploadFiles = async (params: {
files: FileList | File[];
tableName?: string;
fieldName?: string;
recordId?: string | number;
docType?: string;
docTypeName?: string;
targetObjid?: string;
parentTargetObjid?: string;
linkedTable?: string;
linkedField?: string;
autoLink?: boolean;
columnName?: string;
isVirtualFileColumn?: boolean;
companyCode?: string; // 🔒 멀티테넌시: 회사 코드
isRecordMode?: boolean; // 🆕 레코드 모드 플래그
}): Promise<FileUploadResponse> => {
const formData = new FormData();
// 파일 추가
const fileArray = Array.isArray(params.files) ? params.files : Array.from(params.files);
fileArray.forEach((file) => {
formData.append("files", file);
});
// 추가 파라미터들 추가
if (params.tableName) formData.append("tableName", params.tableName);
if (params.fieldName) formData.append("fieldName", params.fieldName);
if (params.recordId) formData.append("recordId", String(params.recordId));
if (params.docType) formData.append("docType", params.docType);
if (params.docTypeName) formData.append("docTypeName", params.docTypeName);
if (params.targetObjid) formData.append("targetObjid", params.targetObjid);
if (params.parentTargetObjid) formData.append("parentTargetObjid", params.parentTargetObjid);
if (params.linkedTable) formData.append("linkedTable", params.linkedTable);
if (params.linkedField) formData.append("linkedField", params.linkedField);
if (params.autoLink !== undefined) formData.append("autoLink", params.autoLink.toString());
if (params.columnName) formData.append("columnName", params.columnName);
if (params.isVirtualFileColumn !== undefined) formData.append("isVirtualFileColumn", params.isVirtualFileColumn.toString());
if (params.companyCode) formData.append("companyCode", params.companyCode); // 🔒 멀티테넌시
// 🆕 레코드 모드 플래그 추가 (백엔드에서 attachments 컬럼 자동 업데이트용)
if (params.isRecordMode !== undefined) formData.append("isRecordMode", params.isRecordMode.toString());
const response = await apiClient.post("/files/upload", formData, {
headers: {
"Content-Type": undefined, // axios가 자동으로 multipart/form-data를 설정하도록
},
});
return response.data;
};
/**
* 파일 다운로드
*/
export const downloadFile = async (params: FileDownloadParams): Promise<void> => {
try {
console.log("📥 downloadFile 호출:", params);
const response = await apiClient.get(`/files/download/${params.fileId}`, {
params: {
serverFilename: params.serverFilename,
originalName: params.originalName,
},
responseType: "blob", // 파일 다운로드를 위해 blob 타입으로 설정
});
console.log("📥 다운로드 응답:", response);
// Blob URL 생성
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
// 다운로드 링크 생성 및 클릭
const link = document.createElement("a");
link.href = url;
link.download = params.originalName;
document.body.appendChild(link);
link.click();
// 정리
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("파일 다운로드 오류:", error);
throw new Error("파일 다운로드에 실패했습니다.");
}
};
/**
* 파일 삭제
*/
export const deleteFile = async (fileId: string, serverFilename: string): Promise<void> => {
const response = await apiClient.delete(`/files/${fileId}`, {
data: { serverFilename },
});
if (!response.data.success) {
throw new Error(response.data.message || "파일 삭제에 실패했습니다.");
}
};
/**
* 파일 정보 조회
*/
export const getFileInfo = async (fileId: string, serverFilename: string) => {
const response = await apiClient.get(`/files/info/${fileId}`, {
params: { serverFilename },
});
return response.data;
};
/**
* 컴포넌트의 템플릿 파일과 데이터 파일을 모두 조회
*/
export const getComponentFiles = async (params: {
screenId: number;
componentId: string;
tableName?: string;
recordId?: string;
columnName?: string;
}): Promise<{
success: boolean;
templateFiles: FileInfo[];
dataFiles: FileInfo[];
totalFiles: FileInfo[];
}> => {
const response = await apiClient.get('/files/component-files', {
params,
});
return response.data;
};
/**
* 파일 업로드 및 JSON 데이터 생성
* InteractiveScreenViewer에서 사용할 통합 함수
*/
export const uploadFilesAndCreateData = async (files: FileList) => {
try {
// 1. 파일 업로드
const uploadResponse = await uploadFiles(files);
if (!uploadResponse.success) {
throw new Error(uploadResponse.message);
}
// 2. JSON 데이터 구조 생성
const fileData = {
files: uploadResponse.files.map((file) => ({
id: file.id,
name: file.name,
size: file.size,
type: file.type,
extension: file.extension,
uploadedAt: file.uploadedAt,
lastModified: file.lastModified,
serverFilename: file.serverFilename, // 다운로드에 필요
})),
totalCount: uploadResponse.files.length,
totalSize: uploadResponse.files.reduce((sum, file) => sum + file.size, 0),
lastModified: new Date().toISOString(),
};
return {
success: true,
data: fileData,
message: uploadResponse.message,
};
} catch (error) {
console.error("파일 업로드 및 데이터 생성 오류:", error);
throw error;
}
};
/**
* 테이블 연결된 파일 조회
*/
export const getLinkedFiles = async (
tableName: string,
recordId: string,
): Promise<{
success: boolean;
files: any[];
totalCount: number;
targetObjid: string;
}> => {
try {
console.log("📎 연결된 파일 조회:", { tableName, recordId });
const response = await apiClient.get(`/files/linked/${tableName}/${recordId}`);
console.log("✅ 연결된 파일 조회 성공:", response.data);
return response.data;
} catch (error) {
console.error("연결된 파일 조회 오류:", error);
throw new Error("연결된 파일 조회에 실패했습니다.");
}
};
/**
* 파일 미리보기 URL 생성
*/
export const getFilePreviewUrl = (fileId: string): string => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "/api";
return `${baseUrl}/files/preview/${fileId}`;
};
/**
* 파일 다운로드 URL 생성
*/
export const getFileDownloadUrl = (fileId: string): string => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "/api";
return `${baseUrl}/files/download/${fileId}`;
};
/**
* 직접 파일 경로 URL 생성 (정적 파일 서빙)
*/
export const getDirectFileUrl = (filePath: string): string => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL?.replace("/api", "") || "";
return `${baseUrl}${filePath}`;
};
/**
* 대표 파일 설정
*/
export const setRepresentativeFile = async (objid: string): Promise<{
success: boolean;
message: string;
}> => {
try {
const response = await apiClient.put(`/files/representative/${objid}`);
return response.data;
} catch (error) {
console.error("대표 파일 설정 오류:", error);
throw new Error("대표 파일 설정에 실패했습니다.");
}
};