import React, { useState, useRef, useCallback, useEffect } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; import { uploadFiles, downloadFile, deleteFile, getComponentFiles } from "@/lib/api/file"; import { GlobalFileManager } from "@/lib/api/globalFile"; import { formatFileSize } from "@/lib/utils"; import { FileViewerModal } from "./FileViewerModal"; import { FileManagerModal } from "./FileManagerModal"; import { FileUploadConfig, FileInfo, FileUploadStatus, FileUploadResponse } from "./types"; import { Upload, File, FileText, Image, Video, Music, Archive, Download, Eye, Trash2, AlertCircle, FileImage, FileVideo, FileAudio, Presentation, } from "lucide-react"; // 파일 아이콘 매핑 const getFileIcon = (extension: string) => { const ext = extension.toLowerCase().replace(".", ""); if (["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"].includes(ext)) { return ; } if (["mp4", "avi", "mov", "wmv", "flv", "webm"].includes(ext)) { return ; } if (["mp3", "wav", "flac", "aac", "ogg"].includes(ext)) { return ; } if (["pdf"].includes(ext)) { return ; } if (["doc", "docx", "hwp", "hwpx", "pages"].includes(ext)) { return ; } if (["xls", "xlsx", "hcell", "numbers"].includes(ext)) { return ; } if (["ppt", "pptx", "hanshow", "keynote"].includes(ext)) { return ; } if (["zip", "rar", "7z", "tar", "gz"].includes(ext)) { return ; } return ; }; export interface FileUploadComponentProps { component: any; componentConfig: FileUploadConfig; componentStyle: React.CSSProperties; className: string; isInteractive: boolean; isDesignMode: boolean; formData: any; onFormDataChange: (data: any) => void; onClick?: () => void; onDragStart?: (e: React.DragEvent) => void; onDragEnd?: (e: React.DragEvent) => void; onUpdate?: (updates: Partial) => void; autoGeneration?: any; hidden?: boolean; onConfigChange?: (config: any) => void; } const FileUploadComponent: React.FC = ({ component, componentConfig, componentStyle, className, isInteractive, isDesignMode = false, // 기본값 설정 formData, onFormDataChange, onClick, onDragStart, onDragEnd, onUpdate, }) => { const [uploadedFiles, setUploadedFiles] = useState([]); const [uploadStatus, setUploadStatus] = useState("idle"); const [dragOver, setDragOver] = useState(false); const [viewerFile, setViewerFile] = useState(null); const [isViewerOpen, setIsViewerOpen] = useState(false); const [isFileManagerOpen, setIsFileManagerOpen] = useState(false); const [forceUpdate, setForceUpdate] = useState(0); const fileInputRef = useRef(null); // 컴포넌트 마운트 시 즉시 localStorage에서 파일 복원 useEffect(() => { if (!component?.id) return; try { const backupKey = `fileUpload_${component.id}`; const backupFiles = localStorage.getItem(backupKey); if (backupFiles) { const parsedFiles = JSON.parse(backupFiles); if (parsedFiles.length > 0) { console.log("🚀 컴포넌트 마운트 시 파일 즉시 복원:", { componentId: component.id, restoredFiles: parsedFiles.length, files: parsedFiles.map((f: any) => ({ objid: f.objid, name: f.realFileName })), }); setUploadedFiles(parsedFiles); // 전역 상태에도 복원 if (typeof window !== "undefined") { (window as any).globalFileState = { ...(window as any).globalFileState, [component.id]: parsedFiles, }; } } } } catch (e) { console.warn("컴포넌트 마운트 시 파일 복원 실패:", e); } }, [component.id]); // component.id가 변경될 때만 실행 // 🎯 화면설계 모드에서 실제 화면으로의 실시간 동기화 이벤트 리스너 useEffect(() => { const handleDesignModeFileChange = (event: CustomEvent) => { console.log("🎯🎯🎯 FileUploadComponent 화면설계 모드 파일 변경 이벤트 수신:", { eventComponentId: event.detail.componentId, currentComponentId: component.id, isMatch: event.detail.componentId === component.id, filesCount: event.detail.files?.length || 0, action: event.detail.action, source: event.detail.source, eventDetail: event.detail, }); // 현재 컴포넌트와 일치하고 화면설계 모드에서 온 이벤트인 경우 if (event.detail.componentId === component.id && event.detail.source === "designMode") { console.log("✅✅✅ 화면설계 모드 → 실제 화면 파일 동기화 시작:", { componentId: component.id, filesCount: event.detail.files?.length || 0, action: event.detail.action, }); // 파일 상태 업데이트 const newFiles = event.detail.files || []; setUploadedFiles(newFiles); // localStorage 백업 업데이트 try { const backupKey = `fileUpload_${component.id}`; localStorage.setItem(backupKey, JSON.stringify(newFiles)); console.log("💾 화면설계 모드 동기화 후 localStorage 백업 업데이트:", { componentId: component.id, fileCount: newFiles.length, }); } catch (e) { console.warn("localStorage 백업 업데이트 실패:", e); } // 전역 상태 업데이트 if (typeof window !== "undefined") { (window as any).globalFileState = { ...(window as any).globalFileState, [component.id]: newFiles, }; } // onUpdate 콜백 호출 (부모 컴포넌트에 알림) if (onUpdate) { onUpdate({ uploadedFiles: newFiles, lastFileUpdate: event.detail.timestamp, }); } console.log("🎉🎉🎉 화면설계 모드 → 실제 화면 동기화 완료:", { componentId: component.id, finalFileCount: newFiles.length, }); } }; if (typeof window !== "undefined") { window.addEventListener("globalFileStateChanged", handleDesignModeFileChange as EventListener); return () => { window.removeEventListener("globalFileStateChanged", handleDesignModeFileChange as EventListener); }; } }, [component.id, onUpdate]); // 템플릿 파일과 데이터 파일을 조회하는 함수 const loadComponentFiles = useCallback(async () => { if (!component?.id) return; try { let screenId = formData?.screenId || (typeof window !== "undefined" && window.location.pathname.includes("/screens/") ? parseInt(window.location.pathname.split("/screens/")[1]) : null); // 디자인 모드인 경우 기본 화면 ID 사용 if (!screenId && isDesignMode) { screenId = 40; // 기본 화면 ID console.log("📂 디자인 모드: 기본 화면 ID 사용 (40)"); } if (!screenId) { console.log("📂 화면 ID 없음, 기존 파일 로직 사용"); return false; // 기존 로직 사용 } const params = { screenId, componentId: component.id, tableName: formData?.tableName || component.tableName, recordId: formData?.id, columnName: component.columnName, }; console.log("📂 컴포넌트 파일 조회:", params); const response = await getComponentFiles(params); if (response.success) { console.log("📁 파일 조회 결과:", { templateFiles: response.templateFiles.length, dataFiles: response.dataFiles.length, totalFiles: response.totalFiles.length, summary: response.summary, actualFiles: response.totalFiles, }); // 파일 데이터 형식 통일 const formattedFiles = response.totalFiles.map((file: any) => ({ objid: file.objid || file.id, savedFileName: file.savedFileName || file.saved_file_name, realFileName: file.realFileName || file.real_file_name, fileSize: file.fileSize || file.file_size, fileExt: file.fileExt || file.file_ext, regdate: file.regdate, status: file.status || "ACTIVE", uploadedAt: file.uploadedAt || new Date().toISOString(), ...file, })); console.log("📁 형식 변환된 파일 데이터:", formattedFiles); // 🔄 localStorage의 기존 파일과 서버 파일 병합 let finalFiles = formattedFiles; try { const backupKey = `fileUpload_${component.id}`; const backupFiles = localStorage.getItem(backupKey); if (backupFiles) { const parsedBackupFiles = JSON.parse(backupFiles); // 서버에 없는 localStorage 파일들을 추가 (objid 기준으로 중복 제거) const serverObjIds = new Set(formattedFiles.map((f: any) => f.objid)); const additionalFiles = parsedBackupFiles.filter((f: any) => !serverObjIds.has(f.objid)); finalFiles = [...formattedFiles, ...additionalFiles]; console.log("🔄 파일 병합 완료:", { 서버파일: formattedFiles.length, 로컬파일: parsedBackupFiles.length, 추가파일: additionalFiles.length, 최종파일: finalFiles.length, 최종파일목록: finalFiles.map((f: any) => ({ objid: f.objid, name: f.realFileName })), }); } } catch (e) { console.warn("파일 병합 중 오류:", e); } setUploadedFiles(finalFiles); // 전역 상태에도 저장 if (typeof window !== "undefined") { (window as any).globalFileState = { ...(window as any).globalFileState, [component.id]: finalFiles, }; // 🌐 전역 파일 저장소에 등록 (페이지 간 공유용) GlobalFileManager.registerFiles(finalFiles, { uploadPage: window.location.pathname, componentId: component.id, screenId: formData?.screenId, }); // localStorage 백업도 병합된 파일로 업데이트 try { const backupKey = `fileUpload_${component.id}`; localStorage.setItem(backupKey, JSON.stringify(finalFiles)); console.log("💾 localStorage 백업 업데이트 완료:", finalFiles.length); } catch (e) { console.warn("localStorage 백업 업데이트 실패:", e); } } return true; // 새로운 로직 사용됨 } } catch (error) { console.error("파일 조회 오류:", error); } return false; // 기존 로직 사용 }, [component.id, component.tableName, component.columnName, formData?.screenId, formData?.tableName, formData?.id]); // 컴포넌트 파일 동기화 useEffect(() => { const componentFiles = (component as any)?.uploadedFiles || []; const lastUpdate = (component as any)?.lastFileUpdate; console.log("🔄 FileUploadComponent 파일 동기화 시작:", { componentId: component.id, componentFiles: componentFiles.length, formData: formData, screenId: formData?.screenId, currentUploadedFiles: uploadedFiles.length, }); // 먼저 새로운 템플릿 파일 조회 시도 loadComponentFiles().then((useNewLogic) => { if (useNewLogic) { console.log("✅ 새로운 템플릿 파일 로직 사용"); return; // 새로운 로직이 성공했으면 기존 로직 스킵 } // 기존 로직 사용 console.log("📂 기존 파일 로직 사용"); // 전역 상태에서 최신 파일 정보 가져오기 const globalFileState = typeof window !== "undefined" ? (window as any).globalFileState || {} : {}; const globalFiles = globalFileState[component.id] || []; // 최신 파일 정보 사용 (전역 상태 > 컴포넌트 속성) const currentFiles = globalFiles.length > 0 ? globalFiles : componentFiles; console.log("🔄 FileUploadComponent 파일 동기화:", { componentId: component.id, componentFiles: componentFiles.length, globalFiles: globalFiles.length, currentFiles: currentFiles.length, uploadedFiles: uploadedFiles.length, lastUpdate: lastUpdate, }); // localStorage에서 백업 파일 복원 (새로고침 시 중요!) try { const backupKey = `fileUpload_${component.id}`; const backupFiles = localStorage.getItem(backupKey); if (backupFiles) { const parsedFiles = JSON.parse(backupFiles); if (parsedFiles.length > 0 && currentFiles.length === 0) { console.log("🔄 localStorage에서 파일 복원:", { componentId: component.id, restoredFiles: parsedFiles.length, files: parsedFiles.map((f: any) => ({ objid: f.objid, name: f.realFileName })), }); setUploadedFiles(parsedFiles); // 전역 상태에도 복원 if (typeof window !== "undefined") { (window as any).globalFileState = { ...(window as any).globalFileState, [component.id]: parsedFiles, }; } return; } } } catch (e) { console.warn("localStorage 백업 복원 실패:", e); } // 최신 파일과 현재 파일 비교 if (JSON.stringify(currentFiles) !== JSON.stringify(uploadedFiles)) { console.log("🔄 useEffect에서 파일 목록 변경 감지:", { currentFiles: currentFiles.length, uploadedFiles: uploadedFiles.length, currentFilesData: currentFiles.map((f: any) => ({ objid: f.objid, name: f.realFileName })), uploadedFilesData: uploadedFiles.map((f) => ({ objid: f.objid, name: f.realFileName })), }); setUploadedFiles(currentFiles); setForceUpdate((prev) => prev + 1); } }); }, [loadComponentFiles, component.id, (component as any)?.uploadedFiles, (component as any)?.lastFileUpdate]); // 전역 상태 변경 감지 (모든 파일 컴포넌트 동기화 + 화면 복원) useEffect(() => { const handleGlobalFileStateChange = (event: CustomEvent) => { const { componentId, files, fileCount, timestamp, isRestore } = event.detail; console.log("🔄 FileUploadComponent 전역 상태 변경 감지:", { currentComponentId: component.id, eventComponentId: componentId, isForThisComponent: componentId === component.id, newFileCount: fileCount, currentFileCount: uploadedFiles.length, timestamp, isRestore: !!isRestore, }); // 같은 컴포넌트 ID인 경우에만 업데이트 if (componentId === component.id) { const logMessage = isRestore ? "🔄 화면 복원으로 파일 상태 동기화" : "✅ 파일 상태 동기화 적용"; console.log(logMessage, { componentId: component.id, 이전파일수: uploadedFiles?.length || 0, 새파일수: files?.length || 0, files: files?.map((f: any) => ({ objid: f.objid, name: f.realFileName })) || [], }); setUploadedFiles(files); setForceUpdate((prev) => prev + 1); // localStorage 백업도 업데이트 try { const backupKey = `fileUpload_${component.id}`; localStorage.setItem(backupKey, JSON.stringify(files)); } catch (e) { console.warn("localStorage 백업 실패:", e); } } }; if (typeof window !== "undefined") { window.addEventListener("globalFileStateChanged", handleGlobalFileStateChange as EventListener); return () => { window.removeEventListener("globalFileStateChanged", handleGlobalFileStateChange as EventListener); }; } }, [component.id, uploadedFiles.length]); // 파일 업로드 설정 - componentConfig가 undefined일 수 있으므로 안전하게 처리 const safeComponentConfig = componentConfig || {}; const fileConfig = { accept: safeComponentConfig.accept || "*/*", multiple: safeComponentConfig.multiple || false, maxSize: safeComponentConfig.maxSize || 10 * 1024 * 1024, // 10MB maxFiles: safeComponentConfig.maxFiles || 5, ...safeComponentConfig, } as FileUploadConfig; // 파일 선택 핸들러 const handleFileSelect = useCallback(() => { console.log("🎯 handleFileSelect 호출됨:", { hasFileInputRef: !!fileInputRef.current, fileInputRef: fileInputRef.current, fileInputType: fileInputRef.current?.type, fileInputHidden: fileInputRef.current?.className, }); if (fileInputRef.current) { console.log("✅ fileInputRef.current.click() 호출"); fileInputRef.current.click(); } else { console.log("❌ fileInputRef.current가 null입니다"); } }, []); const handleInputChange = useCallback((e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (files.length > 0) { handleFileUpload(files); } }, []); // 파일 업로드 처리 const handleFileUpload = useCallback( async (files: File[]) => { if (!files.length) return; // 중복 파일 체크 const existingFileNames = uploadedFiles.map((f) => f.realFileName.toLowerCase()); const duplicates: string[] = []; const uniqueFiles: File[] = []; console.log("🔍 중복 파일 체크:", { uploadedFiles: uploadedFiles.length, existingFileNames: existingFileNames, newFiles: files.map((f) => f.name.toLowerCase()), }); files.forEach((file) => { const fileName = file.name.toLowerCase(); if (existingFileNames.includes(fileName)) { duplicates.push(file.name); console.log("❌ 중복 파일 발견:", file.name); } else { uniqueFiles.push(file); console.log("✅ 새로운 파일:", file.name); } }); console.log("🔍 중복 체크 결과:", { duplicates: duplicates, uniqueFiles: uniqueFiles.map((f) => f.name), }); if (duplicates.length > 0) { toast.error(`중복된 파일이 있습니다: ${duplicates.join(", ")}`, { description: "같은 이름의 파일이 이미 업로드되어 있습니다.", duration: 4000, }); if (uniqueFiles.length === 0) { return; // 모든 파일이 중복이면 업로드 중단 } // 일부만 중복인 경우 고유한 파일만 업로드 toast.info(`${uniqueFiles.length}개의 새로운 파일만 업로드합니다.`); } const filesToUpload = uniqueFiles.length > 0 ? uniqueFiles : files; setUploadStatus("uploading"); toast.loading("파일을 업로드하는 중...", { id: "file-upload" }); try { // targetObjid 생성 - 템플릿 vs 데이터 파일 구분 const tableName = formData?.tableName || component.tableName || "default_table"; const recordId = formData?.id; const screenId = formData?.screenId; const columnName = component.columnName || component.id; let targetObjid; if (recordId && tableName) { // 실제 데이터 파일 targetObjid = `${tableName}:${recordId}:${columnName}`; console.log("📁 실제 데이터 파일 업로드:", targetObjid); } else if (screenId) { // 템플릿 파일 targetObjid = `screen_${screenId}:${component.id}`; console.log("🎨 템플릿 파일 업로드:", targetObjid); } else { // 기본값 (화면관리에서 사용) targetObjid = `temp_${component.id}`; console.log("📝 기본 파일 업로드:", targetObjid); } const uploadData = { // 🎯 formData에서 백엔드 API 설정 가져오기 autoLink: formData?.autoLink || true, linkedTable: formData?.linkedTable || tableName, recordId: formData?.recordId || recordId || `temp_${component.id}`, columnName: formData?.columnName || columnName, isVirtualFileColumn: formData?.isVirtualFileColumn || true, docType: component.fileConfig?.docType || "DOCUMENT", docTypeName: component.fileConfig?.docTypeName || "일반 문서", // 호환성을 위한 기존 필드들 tableName: tableName, fieldName: columnName, targetObjid: targetObjid, // InteractiveDataTable 호환을 위한 targetObjid 추가 }; console.log("📤 파일 업로드 시작:", { originalFiles: files.length, filesToUpload: filesToUpload.length, files: filesToUpload.map((f) => ({ name: f.name, size: f.size })), uploadData, }); const response = await uploadFiles({ files: filesToUpload, ...uploadData, }); console.log("📤 파일 업로드 API 응답:", response); if (response.success) { // FileUploadResponse 타입에 맞게 files 배열 사용 const fileData = response.files || (response as any).data || []; console.log("📁 파일 데이터 확인:", { hasFiles: !!response.files, hasData: !!(response as any).data, fileDataLength: fileData.length, fileData: fileData, responseKeys: Object.keys(response), }); if (fileData.length === 0) { throw new Error("업로드된 파일 데이터를 받지 못했습니다."); } const newFiles = fileData.map((file: any) => ({ objid: file.objid || file.id, savedFileName: file.saved_file_name || file.savedFileName, realFileName: file.real_file_name || file.realFileName || file.name, fileSize: file.file_size || file.fileSize || file.size, fileExt: file.file_ext || file.fileExt || file.extension, filePath: file.file_path || file.filePath || file.path, docType: file.doc_type || file.docType, docTypeName: file.doc_type_name || file.docTypeName, targetObjid: file.target_objid || file.targetObjid, parentTargetObjid: file.parent_target_objid || file.parentTargetObjid, companyCode: file.company_code || file.companyCode, writer: file.writer, regdate: file.regdate, status: file.status || "ACTIVE", uploadedAt: new Date().toISOString(), ...file, })); console.log("📁 변환된 파일 데이터:", newFiles); const updatedFiles = [...uploadedFiles, ...newFiles]; console.log("🔄 파일 상태 업데이트:", { 이전파일수: uploadedFiles.length, 새파일수: newFiles.length, 총파일수: updatedFiles.length, updatedFiles: updatedFiles.map((f) => ({ objid: f.objid, name: f.realFileName })), }); setUploadedFiles(updatedFiles); setUploadStatus("success"); // localStorage 백업 try { const backupKey = `fileUpload_${component.id}`; localStorage.setItem(backupKey, JSON.stringify(updatedFiles)); } catch (e) { console.warn("localStorage 백업 실패:", e); } // 전역 상태 업데이트 (모든 파일 컴포넌트 동기화) if (typeof window !== "undefined") { // 전역 파일 상태 업데이트 const globalFileState = (window as any).globalFileState || {}; globalFileState[component.id] = updatedFiles; (window as any).globalFileState = globalFileState; // 🌐 전역 파일 저장소에 새 파일 등록 (페이지 간 공유용) GlobalFileManager.registerFiles(newFiles, { uploadPage: window.location.pathname, componentId: component.id, screenId: formData?.screenId, }); // 모든 파일 컴포넌트에 동기화 이벤트 발생 const syncEvent = new CustomEvent("globalFileStateChanged", { detail: { componentId: component.id, files: updatedFiles, fileCount: updatedFiles.length, timestamp: Date.now(), }, }); window.dispatchEvent(syncEvent); console.log("🌐 전역 파일 상태 업데이트 및 동기화 이벤트 발생:", { componentId: component.id, fileCount: updatedFiles.length, globalState: Object.keys(globalFileState).map((id) => ({ id, fileCount: globalFileState[id]?.length || 0, })), }); } // 컴포넌트 업데이트 if (onUpdate) { const timestamp = Date.now(); console.log("🔄 onUpdate 호출:", { componentId: component.id, uploadedFiles: updatedFiles.length, timestamp: timestamp, }); onUpdate({ uploadedFiles: updatedFiles, lastFileUpdate: timestamp, }); } else { console.warn("⚠️ onUpdate 콜백이 없습니다!"); } // 그리드 파일 상태 새로고침 이벤트 발생 if (typeof window !== "undefined") { const refreshEvent = new CustomEvent("refreshFileStatus", { detail: { tableName: tableName, recordId: recordId, columnName: columnName, targetObjid: targetObjid, fileCount: updatedFiles.length, }, }); window.dispatchEvent(refreshEvent); console.log("🔄 그리드 파일 상태 새로고침 이벤트 발생:", { tableName, recordId, columnName, targetObjid, fileCount: updatedFiles.length, }); } // 폼 데이터 업데이트 if (onFormDataChange && component.columnName) { const fileIds = updatedFiles.map((f) => f.objid); onFormDataChange({ ...formData, [component.columnName]: fileIds, }); } // 컴포넌트 설정 콜백 if (safeComponentConfig.onFileUpload) { safeComponentConfig.onFileUpload(newFiles); } // 성공 시 토스트 처리 setUploadStatus("idle"); toast.dismiss("file-upload"); toast.success(`${newFiles.length}개 파일 업로드 완료`); } else { console.error("❌ 파일 업로드 실패:", response); throw new Error(response.message || (response as any).error || "파일 업로드에 실패했습니다."); } } catch (error) { console.error("파일 업로드 오류:", error); setUploadStatus("error"); toast.dismiss("file-upload"); toast.error(`파일 업로드 오류: ${error instanceof Error ? error.message : "알 수 없는 오류"}`); } }, [safeComponentConfig, uploadedFiles, onFormDataChange, component.columnName, component.id, formData], ); // 파일 뷰어 열기 const handleFileView = useCallback((file: FileInfo) => { setViewerFile(file); setIsViewerOpen(true); }, []); // 파일 뷰어 닫기 const handleViewerClose = useCallback(() => { setIsViewerOpen(false); setViewerFile(null); }, []); // 파일 다운로드 const handleFileDownload = useCallback(async (file: FileInfo) => { try { await downloadFile({ fileId: file.objid, serverFilename: file.savedFileName, originalName: file.realFileName, }); toast.success(`${file.realFileName} 다운로드 완료`); } catch (error) { console.error("파일 다운로드 오류:", error); toast.error("파일 다운로드에 실패했습니다."); } }, []); // 파일 삭제 const handleFileDelete = useCallback( async (file: FileInfo | string) => { try { const fileId = typeof file === "string" ? file : file.objid; const fileName = typeof file === "string" ? "파일" : file.realFileName; const serverFilename = typeof file === "string" ? "temp_file" : file.savedFileName; await deleteFile(fileId, serverFilename); const updatedFiles = uploadedFiles.filter((f) => f.objid !== fileId); setUploadedFiles(updatedFiles); // localStorage 백업 업데이트 try { const backupKey = `fileUpload_${component.id}`; localStorage.setItem(backupKey, JSON.stringify(updatedFiles)); } catch (e) { console.warn("localStorage 백업 업데이트 실패:", e); } // 전역 상태 업데이트 (모든 파일 컴포넌트 동기화) if (typeof window !== "undefined") { // 전역 파일 상태 업데이트 const globalFileState = (window as any).globalFileState || {}; globalFileState[component.id] = updatedFiles; (window as any).globalFileState = globalFileState; // 모든 파일 컴포넌트에 동기화 이벤트 발생 const syncEvent = new CustomEvent("globalFileStateChanged", { detail: { componentId: component.id, files: updatedFiles, fileCount: updatedFiles.length, timestamp: Date.now(), source: "realScreen", // 🎯 실제 화면에서 온 이벤트임을 표시 action: "delete", }, }); window.dispatchEvent(syncEvent); console.log("🗑️ 파일 삭제 후 전역 상태 동기화:", { componentId: component.id, deletedFile: fileName, remainingFiles: updatedFiles.length, }); } // 컴포넌트 업데이트 if (onUpdate) { const timestamp = Date.now(); onUpdate({ uploadedFiles: updatedFiles, lastFileUpdate: timestamp, }); } toast.success(`${fileName} 삭제 완료`); } catch (error) { console.error("파일 삭제 오류:", error); toast.error("파일 삭제에 실패했습니다."); } }, [uploadedFiles, onUpdate, component.id], ); // 드래그 앤 드롭 핸들러 const handleDragOver = useCallback( (e: React.DragEvent) => { console.log("🎯 드래그 오버 이벤트 감지:", { readonly: safeComponentConfig.readonly, disabled: safeComponentConfig.disabled, dragOver: dragOver, }); e.preventDefault(); e.stopPropagation(); if (!safeComponentConfig.readonly && !safeComponentConfig.disabled) { setDragOver(true); console.log("✅ 드래그 오버 활성화"); } else { console.log("❌ 드래그 차단됨: readonly 또는 disabled"); } }, [safeComponentConfig.readonly, safeComponentConfig.disabled, dragOver], ); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragOver(false); }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragOver(false); if (!safeComponentConfig.readonly && !safeComponentConfig.disabled) { const files = Array.from(e.dataTransfer.files); if (files.length > 0) { handleFileUpload(files); } } }, [safeComponentConfig.readonly, safeComponentConfig.disabled, handleFileUpload], ); // 클릭 핸들러 const handleClick = useCallback( (e: React.MouseEvent) => { console.log("🖱️ 파일 업로드 영역 클릭:", { readonly: safeComponentConfig.readonly, disabled: safeComponentConfig.disabled, hasHandleFileSelect: !!handleFileSelect, }); e.preventDefault(); e.stopPropagation(); if (!safeComponentConfig.readonly && !safeComponentConfig.disabled) { console.log("✅ 파일 선택 함수 호출"); handleFileSelect(); } else { console.log("❌ 클릭 차단됨: readonly 또는 disabled"); } onClick?.(); }, [safeComponentConfig.readonly, safeComponentConfig.disabled, handleFileSelect, onClick], ); return ( {/* 라벨 렌더링 - 주석처리 */} {/* {component.label && component.style?.labelDisplay !== false && ( {component.label} {component.required && ( * )} )} */} {/* 파일 업로드 영역 - 주석처리 */} {/* {!isDesignMode && ( {uploadStatus === 'uploading' ? ( 업로드 중... ) : ( <> 파일 업로드 > )} )} */} {/* 업로드된 파일 목록 - 항상 표시 */} {(() => { const shouldShow = true; // 항상 표시하도록 강제 console.log("🎯🎯🎯 파일 목록 렌더링 조건 체크:", { uploadedFilesLength: uploadedFiles.length, isDesignMode: isDesignMode, shouldShow: shouldShow, uploadedFiles: uploadedFiles.map((f) => ({ objid: f.objid, name: f.realFileName })), "🚨 렌더링 여부": shouldShow ? "✅ 렌더링됨" : "❌ 렌더링 안됨", }); return shouldShow; })() && ( 업로드된 파일 ({uploadedFiles.length}) {uploadedFiles.length > 0 && ( 총 {formatFileSize(uploadedFiles.reduce((sum, file) => sum + file.fileSize, 0))} )} setIsFileManagerOpen(true)} style={{ boxShadow: "none !important", textShadow: "none !important", filter: "none !important", WebkitBoxShadow: "none !important", MozBoxShadow: "none !important", }} > 자세히보기 {uploadedFiles.length > 0 ? ( {uploadedFiles.map((file) => ( {getFileIcon(file.fileExt)} handleFileView(file)} style={{ textShadow: "none" }} > {file.realFileName} {formatFileSize(file.fileSize)} ))} 💡 파일명 클릭으로 미리보기 또는 "전체 자세히보기"로 파일 관리 ) : ( 업로드된 파일이 없습니다 상세설정에서 파일을 업로드하세요 )} )} {/* 도움말 텍스트 */} {safeComponentConfig.helperText && ( {safeComponentConfig.helperText} )} {/* 파일뷰어 모달 */} {/* 파일 관리 모달 */} setIsFileManagerOpen(false)} uploadedFiles={uploadedFiles} onFileUpload={handleFileUpload} onFileDownload={handleFileDownload} onFileDelete={handleFileDelete} onFileView={handleFileView} config={safeComponentConfig} isDesignMode={isDesignMode} /> ); }; export { FileUploadComponent }; export default FileUploadComponent;
파일 업로드
업로드된 파일이 없습니다
상세설정에서 파일을 업로드하세요
{safeComponentConfig.helperText}