Compare commits

..

No commits in common. "420d9c141cf3b7b8e0ba6951954e7dde9c7c70d6" and "fedd75ddf5658f0fa569530f5e7dddb1f6388935" have entirely different histories.

6 changed files with 35 additions and 21 deletions

View File

@ -2230,9 +2230,8 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// value가 objid (숫자 또는 숫자 문자열)인 경우 파일 API URL 사용 // value가 objid (숫자 또는 숫자 문자열)인 경우 파일 API URL 사용
// 🔑 download 대신 preview 사용 (공개 접근 허용) // 🔑 download 대신 preview 사용 (공개 접근 허용)
const isObjid = /^\d+$/.test(String(value)); const isObjid = /^\d+$/.test(String(value));
// 🔑 상대 경로(/api/...) 대신 전체 URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지)
const imageUrl = isObjid const imageUrl = isObjid
? getFilePreviewUrl(String(value)) ? `/api/files/preview/${value}`
: getFullImageUrl(String(value)); : getFullImageUrl(String(value));
return ( return (

View File

@ -1,4 +1,4 @@
import { apiClient, API_BASE_URL } from "./client"; import { apiClient } from "./client";
export interface FileInfo { export interface FileInfo {
id: string; id: string;
@ -231,10 +231,10 @@ export const getLinkedFiles = async (
/** /**
* URL * URL
* 🔑 (/api) API_BASE_URL (Docker Next.js rewrite )
*/ */
export const getFilePreviewUrl = (fileId: string): string => { export const getFilePreviewUrl = (fileId: string): string => {
return `${API_BASE_URL}/files/preview/${fileId}`; const baseUrl = process.env.NEXT_PUBLIC_API_URL || "/api";
return `${baseUrl}/files/preview/${fileId}`;
}; };
/** /**

View File

@ -163,12 +163,17 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
const ext = file.fileExt.toLowerCase().replace('.', ''); const ext = file.fileExt.toLowerCase().replace('.', '');
if (imageExtensions.includes(ext) || file.isImage) { if (imageExtensions.includes(ext) || file.isImage) {
try { try {
// 🔑 이미 previewUrl이 있으면 바로 사용
if (file.previewUrl) {
setPreviewImageUrl(file.previewUrl);
return;
}
// 이전 Blob URL 해제 // 이전 Blob URL 해제
if (previewImageUrl) { if (previewImageUrl) {
URL.revokeObjectURL(previewImageUrl); URL.revokeObjectURL(previewImageUrl);
} }
// 🔑 항상 apiClient를 통해 Blob 다운로드 (Docker 환경에서 상대 경로 문제 방지)
const { apiClient } = await import("@/lib/api/client"); const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.get(`/files/preview/${file.objid}`, { const response = await apiClient.get(`/files/preview/${file.objid}`, {
responseType: 'blob' responseType: 'blob'
@ -179,7 +184,12 @@ export const FileManagerModal: React.FC<FileManagerModalProps> = ({
setPreviewImageUrl(blobUrl); setPreviewImageUrl(blobUrl);
} catch (error) { } catch (error) {
console.error("이미지 로드 실패:", error); console.error("이미지 로드 실패:", error);
setPreviewImageUrl(null); // 🔑 에러 발생 시에도 previewUrl이 있으면 사용
if (file.previewUrl) {
setPreviewImageUrl(file.previewUrl);
} else {
setPreviewImageUrl(null);
}
} }
} else { } else {
setPreviewImageUrl(null); setPreviewImageUrl(null);

View File

@ -3,7 +3,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { toast } from "sonner"; import { toast } from "sonner";
import { uploadFiles, downloadFile, deleteFile, getComponentFiles, getFileInfoByObjid, getFilePreviewUrl } from "@/lib/api/file"; import { uploadFiles, downloadFile, deleteFile, getComponentFiles, getFileInfoByObjid } from "@/lib/api/file";
import { GlobalFileManager } from "@/lib/api/globalFile"; import { GlobalFileManager } from "@/lib/api/globalFile";
import { formatFileSize } from "@/lib/utils"; import { formatFileSize } from "@/lib/utils";
import { apiClient } from "@/lib/api/client"; import { apiClient } from "@/lib/api/client";
@ -205,7 +205,9 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
return; return;
} }
// 🔑 실제 파일 정보 조회 (previewUrl 제거 - apiClient blob 다운로드 방식으로 통일) const previewUrl = `/api/files/preview/${objidStr}`;
// 🔑 실제 파일 정보 조회
(async () => { (async () => {
try { try {
const fileInfoResponse = await getFileInfoByObjid(objidStr); const fileInfoResponse = await getFileInfoByObjid(objidStr);
@ -218,14 +220,15 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
realFileName: realFileName, realFileName: realFileName,
fileExt: fileExt, fileExt: fileExt,
fileSize: fileSize, fileSize: fileSize,
filePath: getFilePreviewUrl(objidStr), filePath: previewUrl,
regdate: regdate, regdate: regdate,
isImage: true, isImage: true,
previewUrl: previewUrl,
isRepresentative: isRepresentative, isRepresentative: isRepresentative,
}; };
setUploadedFiles([fileInfo]); setUploadedFiles([fileInfo]);
// representativeImageUrl은 loadRepresentativeImage에서 blob으로 로드됨 setRepresentativeImageUrl(previewUrl);
} else { } else {
// 파일 정보 조회 실패 시 최소 정보로 추가 // 파일 정보 조회 실패 시 최소 정보로 추가
console.warn("🖼️ [FileUploadComponent] 파일 정보 조회 실패, 최소 정보 사용"); console.warn("🖼️ [FileUploadComponent] 파일 정보 조회 실패, 최소 정보 사용");
@ -234,13 +237,14 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
realFileName: `image_${objidStr}.jpg`, realFileName: `image_${objidStr}.jpg`,
fileExt: '.jpg', fileExt: '.jpg',
fileSize: 0, fileSize: 0,
filePath: getFilePreviewUrl(objidStr), filePath: previewUrl,
regdate: new Date().toISOString(), regdate: new Date().toISOString(),
isImage: true, isImage: true,
previewUrl: previewUrl,
}; };
setUploadedFiles([minimalFileInfo]); setUploadedFiles([minimalFileInfo]);
// representativeImageUrl은 loadRepresentativeImage에서 blob으로 로드됨 setRepresentativeImageUrl(previewUrl);
} }
} catch (error) { } catch (error) {
console.error("🖼️ [FileUploadComponent] 파일 정보 조회 오류:", error); console.error("🖼️ [FileUploadComponent] 파일 정보 조회 오류:", error);
@ -871,8 +875,14 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
return; return;
} }
// 🔑 이미 previewUrl이 설정된 경우 바로 사용 (API 호출 스킵)
if (file.previewUrl) {
setRepresentativeImageUrl(file.previewUrl);
return;
}
// API 클라이언트를 통해 Blob으로 다운로드 (인증 토큰 포함) // API 클라이언트를 통해 Blob으로 다운로드 (인증 토큰 포함)
// 🔑 previewUrl 상대 경로 대신 apiClient를 사용하여 Docker 환경에서도 정상 동작 // 🔑 download 대신 preview 사용 (공개 접근)
const response = await apiClient.get(`/files/preview/${file.objid}`, { const response = await apiClient.get(`/files/preview/${file.objid}`, {
params: { params: {
serverFilename: file.savedFileName, serverFilename: file.savedFileName,

View File

@ -8,7 +8,6 @@ import { entityJoinApi } from "@/lib/api/entityJoin";
import { codeCache } from "@/lib/caching/codeCache"; import { codeCache } from "@/lib/caching/codeCache";
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization"; import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
import { getFullImageUrl } from "@/lib/api/client"; import { getFullImageUrl } from "@/lib/api/client";
import { getFilePreviewUrl } from "@/lib/api/file";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core"; import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core";
@ -4067,9 +4066,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 🔑 download 대신 preview 사용 (공개 접근 허용) // 🔑 download 대신 preview 사용 (공개 접근 허용)
const strValue = String(value); const strValue = String(value);
const isObjid = /^\d+$/.test(strValue); const isObjid = /^\d+$/.test(strValue);
// 🔑 상대 경로(/api/...) 대신 전체 URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지)
const imageUrl = isObjid const imageUrl = isObjid
? getFilePreviewUrl(strValue) ? `/api/files/preview/${strValue}`
: getFullImageUrl(strValue); : getFullImageUrl(strValue);
return ( return (
<div className="flex justify-center"> <div className="flex justify-center">

View File

@ -20,14 +20,11 @@ const nextConfig = {
}, },
// API 프록시 설정 - 백엔드로 요청 전달 // API 프록시 설정 - 백엔드로 요청 전달
// Docker 환경: SERVER_API_URL=http://backend:3001 사용
// 로컬 개발: http://localhost:8080 사용
async rewrites() { async rewrites() {
const backendUrl = process.env.SERVER_API_URL || "http://localhost:8080";
return [ return [
{ {
source: "/api/:path*", source: "/api/:path*",
destination: `${backendUrl}/api/:path*`, destination: "http://localhost:8080/api/:path*",
}, },
]; ];
}, },