"use client"; import React, { useState, useEffect, useCallback, useRef } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { FileComponent, TableInfo } from "@/types/screen"; import { Plus, X, Upload, File, Image, FileText, Download, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { FileInfo } from "@/lib/registry/components/file-upload/types"; import { uploadFiles, downloadFile, deleteFile } from "@/lib/api/file"; import { formatFileSize } from "@/lib/utils"; import { toast } from "sonner"; interface FileComponentConfigPanelProps { component: FileComponent; onUpdateProperty: (componentId: string, path: string, value: any) => void; currentTable?: TableInfo; currentTableName?: string; } export const FileComponentConfigPanel: React.FC = ({ component, onUpdateProperty, currentTable, currentTableName, }) => { // fileConfig가 없는 경우 초기화 React.useEffect(() => { if (!component.fileConfig) { const defaultFileConfig = { docType: "DOCUMENT", docTypeName: "일반 문서", dragDropText: "파일을 드래그하거나 클릭하여 업로드하세요", maxSize: 10, maxFiles: 5, multiple: true, showPreview: true, showProgress: true, autoLink: false, accept: [], linkedTable: "", linkedField: "", }; onUpdateProperty(component.id, "fileConfig", defaultFileConfig); } }, [component.fileConfig, component.id, onUpdateProperty]); // 로컬 상태 const [localInputs, setLocalInputs] = useState({ docType: component.fileConfig?.docType || "DOCUMENT", docTypeName: component.fileConfig?.docTypeName || "일반 문서", dragDropText: component.fileConfig?.dragDropText || "파일을 드래그하거나 클릭하여 업로드하세요", maxSize: component.fileConfig?.maxSize || 10, maxFiles: component.fileConfig?.maxFiles || 5, newAcceptType: "", linkedTable: component.fileConfig?.linkedTable || "", linkedField: component.fileConfig?.linkedField || "", }); const [localValues, setLocalValues] = useState({ multiple: component.fileConfig?.multiple ?? true, showPreview: component.fileConfig?.showPreview ?? true, showProgress: component.fileConfig?.showProgress ?? true, autoLink: component.fileConfig?.autoLink ?? false, }); const [acceptTypes, setAcceptTypes] = useState(component.fileConfig?.accept || []); // 전역 파일 상태 관리를 window 객체에 저장 (컴포넌트 언마운트 시에도 유지) const getGlobalFileState = (): {[key: string]: FileInfo[]} => { if (typeof window !== 'undefined') { return (window as any).globalFileState || {}; } return {}; }; const setGlobalFileState = (updater: (prev: {[key: string]: FileInfo[]}) => {[key: string]: FileInfo[]}) => { if (typeof window !== 'undefined') { const currentState = getGlobalFileState(); const newState = updater(currentState); (window as any).globalFileState = newState; console.log("🌐 전역 파일 상태 업데이트:", { componentId: component.id, newFileCount: newState[component.id]?.length || 0, totalComponents: Object.keys(newState).length }); // 강제 리렌더링을 위한 이벤트 발생 window.dispatchEvent(new CustomEvent('globalFileStateChanged', { detail: { componentId: component.id, fileCount: newState[component.id]?.length || 0 } })); // 디버깅용 전역 함수 등록 (window as any).debugFileState = () => { console.log("🔍 전역 파일 상태 디버깅:", { globalState: (window as any).globalFileState, localStorage: Object.keys(localStorage).filter(key => key.startsWith('fileComponent_')).map(key => ({ key, data: JSON.parse(localStorage.getItem(key) || '[]') })) }); }; } }; // 파일 업로드 관련 상태 - 초기화 시 전역 상태에서 복원 const initializeUploadedFiles = (): FileInfo[] => { const componentFiles = component.uploadedFiles || []; const globalFiles = getGlobalFileState()[component.id] || []; // localStorage 백업에서 복원 (영구 저장된 파일 + 임시 파일) const backupKey = `fileComponent_${component.id}_files`; const tempBackupKey = `fileComponent_${component.id}_files_temp`; const backupFiles = localStorage.getItem(backupKey); const tempBackupFiles = localStorage.getItem(tempBackupKey); let parsedBackupFiles: FileInfo[] = []; let parsedTempFiles: FileInfo[] = []; if (backupFiles) { try { parsedBackupFiles = JSON.parse(backupFiles); } catch (error) { console.error("백업 파일 파싱 실패:", error); } } if (tempBackupFiles) { try { parsedTempFiles = JSON.parse(tempBackupFiles); } catch (error) { console.error("임시 파일 파싱 실패:", error); } } // 우선순위: 전역 상태 > localStorage > 임시 파일 > 컴포넌트 속성 const finalFiles = globalFiles.length > 0 ? globalFiles : parsedBackupFiles.length > 0 ? parsedBackupFiles : parsedTempFiles.length > 0 ? parsedTempFiles : componentFiles; console.log("🚀 FileComponentConfigPanel 초기화:", { componentId: component.id, componentFiles: componentFiles.length, globalFiles: globalFiles.length, backupFiles: parsedBackupFiles.length, tempFiles: parsedTempFiles.length, finalFiles: finalFiles.length, source: globalFiles.length > 0 ? 'global' : parsedBackupFiles.length > 0 ? 'localStorage' : parsedTempFiles.length > 0 ? 'temp' : 'component' }); return finalFiles; }; const [uploadedFiles, setUploadedFiles] = useState(() => { const initialFiles = initializeUploadedFiles(); // 초기화된 파일이 있고 컴포넌트 속성과 다르면 즉시 동기화 if (initialFiles.length > 0 && JSON.stringify(initialFiles) !== JSON.stringify(component.uploadedFiles || [])) { setTimeout(() => { onUpdateProperty(component.id, "uploadedFiles", initialFiles); onUpdateProperty(component.id, "lastFileUpdate", Date.now()); console.log("🔄 초기화 시 컴포넌트 속성 동기화:", { componentId: component.id, fileCount: initialFiles.length }); }, 0); } return initialFiles; }); const [dragOver, setDragOver] = useState(false); const [uploading, setUploading] = useState(false); // 이전 컴포넌트 ID 추적용 ref const prevComponentIdRef = useRef(component.id); // 파일 타입별 아이콘 반환 const getFileIcon = (fileExt: string) => { const ext = fileExt.toLowerCase(); if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext)) { return ; } if (['pdf', 'doc', 'docx', 'txt', 'rtf'].includes(ext)) { return ; } return ; }; // 파일 업로드 처리 const handleFileUpload = useCallback(async (files: FileList | File[]) => { if (!files || files.length === 0) return; const fileArray = Array.from(files); const validFiles: File[] = []; // 파일 검증 for (const file of fileArray) { if (file.size > localInputs.maxSize * 1024 * 1024) { toast.error(`${file.name}: 파일 크기가 ${localInputs.maxSize}MB를 초과합니다.`); continue; } // 파일 타입 검증 (acceptTypes가 설정된 경우에만) if (acceptTypes.length > 0) { const fileExt = '.' + file.name.split('.').pop()?.toLowerCase(); const isAllowed = acceptTypes.some(type => type === '*/*' || type === file.type || type === fileExt || (type.startsWith('.') && fileExt === type) || (type.includes('/*') && file.type.startsWith(type.split('/')[0])) ); if (!isAllowed) { toast.error(`${file.name}: 허용되지 않는 파일 형식입니다. (허용: ${acceptTypes.join(', ')})`); console.log(`파일 검증 실패:`, { fileName: file.name, fileType: file.type, fileExt, acceptTypes, isAllowed }); continue; } } console.log(`파일 검증 성공:`, { fileName: file.name, fileType: file.type, fileSize: file.size, acceptTypesCount: acceptTypes.length }); validFiles.push(file); } if (validFiles.length === 0) return; // 중복 파일 체크 const existingFiles = uploadedFiles; const existingFileNames = existingFiles.map(f => f.realFileName.toLowerCase()); const duplicates: string[] = []; const uniqueFiles: File[] = []; console.log("🔍 중복 파일 체크:", { uploadedFiles: existingFiles.length, existingFileNames: existingFileNames, newFiles: validFiles.map(f => f.name.toLowerCase()) }); validFiles.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 : validFiles; try { console.log("🔄 파일 업로드 시작:", { originalFiles: validFiles.length, filesToUpload: filesToUpload.length, uploading }); setUploading(true); toast.loading(`${filesToUpload.length}개 파일 업로드 중...`); // 그리드와 연동되는 targetObjid 생성 (화면 복원 시스템과 통일) const tableName = 'screen_files'; const screenId = (window as any).__CURRENT_SCREEN_ID__ || 'unknown'; // 현재 화면 ID const componentId = component.id; const fieldName = component.columnName || component.id || 'file_attachment'; const targetObjid = `${tableName}:${screenId}:${componentId}:${fieldName}`; const response = await uploadFiles({ files: filesToUpload, tableName: tableName, fieldName: fieldName, recordId: `${screenId}:${componentId}`, // 화면ID:컴포넌트ID 형태 docType: localInputs.docType, docTypeName: localInputs.docTypeName, targetObjid: targetObjid, // 그리드 연동을 위한 targetObjid columnName: fieldName, isVirtualFileColumn: true, // 가상 파일 컬럼으로 처리 }); console.log("📤 파일 업로드 응답:", response); if (response.success && (response.data || response.files)) { const filesData = response.data || response.files; console.log("📁 업로드된 파일 데이터:", filesData); const newFiles: FileInfo[] = filesData.map((file: any) => ({ objid: file.objid || `temp_${Date.now()}_${Math.random()}`, savedFileName: file.saved_file_name || file.savedFileName, realFileName: file.real_file_name || file.realFileName, fileSize: file.file_size || file.fileSize, fileExt: file.file_ext || file.fileExt, filePath: file.file_path || file.filePath, docType: localInputs.docType, docTypeName: localInputs.docTypeName, targetObjid: file.target_objid || file.targetObjid || component.id, parentTargetObjid: file.parent_target_objid || file.parentTargetObjid, companyCode: file.company_code || file.companyCode || 'DEFAULT', writer: file.writer || 'user', regdate: file.regdate || new Date().toISOString(), status: file.status || 'ACTIVE', path: file.file_path || file.filePath, name: file.real_file_name || file.realFileName, id: file.objid, size: file.file_size || file.fileSize, type: localInputs.docType, uploadedAt: file.regdate || new Date().toISOString(), })); const updatedFiles = localValues.multiple ? [...uploadedFiles, ...newFiles] : newFiles; setUploadedFiles(updatedFiles); // 자동으로 영구 저장 (저장 버튼 없이 바로 저장) const timestamp = Date.now(); // 전역 상태에 저장 setGlobalFileState(prev => ({ ...prev, [component.id]: updatedFiles })); // 컴포넌트 속성에 저장 onUpdateProperty(component.id, "uploadedFiles", updatedFiles); onUpdateProperty(component.id, "lastFileUpdate", timestamp); // localStorage에 영구 저장 const backupKey = `fileComponent_${component.id}_files`; localStorage.setItem(backupKey, JSON.stringify(updatedFiles)); console.log("🔄 FileComponentConfigPanel 자동 저장:", { componentId: component.id, uploadedFiles: updatedFiles.length, status: "자동 영구 저장됨", onUpdatePropertyExists: typeof onUpdateProperty === 'function', globalFileStateUpdated: getGlobalFileState()[component.id]?.length || 0, localStorageBackup: localStorage.getItem(`fileComponent_${component.id}_files`) ? 'saved' : 'not saved' }); // 그리드 파일 상태 새로고침 이벤트 발생 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("🔄 FileComponentConfigPanel 그리드 새로고침 이벤트 발생:", { tableName, recordId, columnName, targetObjid, fileCount: updatedFiles.length }); } toast.dismiss(); toast.success(`${validFiles.length}개 파일이 성공적으로 업로드되었습니다.`); console.log("✅ 파일 업로드 성공:", { newFilesCount: newFiles.length, totalFiles: updatedFiles.length, componentId: component.id, updatedFiles: updatedFiles.map(f => ({ objid: f.objid, name: f.realFileName })) }); } else { throw new Error(response.message || '파일 업로드에 실패했습니다.'); } } catch (error) { console.error('❌ 파일 업로드 오류:', error); toast.dismiss(); toast.error('파일 업로드에 실패했습니다.'); } finally { console.log("🏁 파일 업로드 완료, 로딩 상태 해제"); setUploading(false); } }, [localInputs, localValues, uploadedFiles, onUpdateProperty, currentTableName, component, acceptTypes]); // 파일 다운로드 처리 const handleFileDownload = useCallback(async (file: FileInfo) => { try { await downloadFile({ fileId: file.objid || file.id, serverFilename: file.savedFileName, originalName: file.realFileName || file.name || 'download', }); toast.success(`${file.realFileName || file.name} 다운로드가 완료되었습니다.`); } catch (error) { console.error('파일 다운로드 오류:', error); toast.error('파일 다운로드에 실패했습니다.'); } }, []); // 파일 삭제 처리 const handleFileDelete = useCallback(async (fileId: string) => { try { await deleteFile(fileId); const updatedFiles = uploadedFiles.filter(file => file.objid !== fileId && file.id !== fileId); setUploadedFiles(updatedFiles); // 전역 상태에도 업데이트 setGlobalFileState(prev => ({ ...prev, [component.id]: updatedFiles })); // 컴포넌트 속성 업데이트 (RealtimePreview 강제 리렌더링용) const timestamp = Date.now(); onUpdateProperty(component.id, "uploadedFiles", updatedFiles); onUpdateProperty(component.id, "lastFileUpdate", timestamp); // localStorage 백업도 업데이트 (영구 저장소와 임시 저장소 모두) const backupKey = `fileComponent_${component.id}_files`; const tempBackupKey = `fileComponent_${component.id}_files_temp`; localStorage.setItem(backupKey, JSON.stringify(updatedFiles)); localStorage.setItem(tempBackupKey, JSON.stringify(updatedFiles)); console.log("🗑️ FileComponentConfigPanel 파일 삭제:", { componentId: component.id, deletedFileId: fileId, remainingFiles: updatedFiles.length, timestamp: timestamp }); // 그리드 파일 상태 새로고침 이벤트 발생 if (typeof window !== 'undefined') { const tableName = currentTableName || 'screen_files'; const recordId = component.id; const columnName = component.columnName || component.id || 'file_attachment'; const targetObjid = `${tableName}:${recordId}:${columnName}`; const refreshEvent = new CustomEvent('refreshFileStatus', { detail: { tableName: tableName, recordId: recordId, columnName: columnName, targetObjid: targetObjid, fileCount: updatedFiles.length } }); window.dispatchEvent(refreshEvent); console.log("🔄 FileComponentConfigPanel 파일 삭제 후 그리드 새로고침:", { tableName, recordId, columnName, targetObjid, fileCount: updatedFiles.length }); } toast.success('파일이 삭제되었습니다.'); } catch (error) { console.error('파일 삭제 오류:', error); toast.error('파일 삭제에 실패했습니다.'); } }, [uploadedFiles, onUpdateProperty, component.id]); // 파일 저장 처리 (임시 → 영구 저장) const handleSaveFiles = useCallback(() => { try { // 컴포넌트 속성에 영구 저장 const timestamp = Date.now(); onUpdateProperty(component.id, "uploadedFiles", uploadedFiles); onUpdateProperty(component.id, "lastFileUpdate", timestamp); // 전역 상태에도 저장 setGlobalFileState(prev => ({ ...prev, [component.id]: uploadedFiles })); // localStorage에도 백업 const backupKey = `fileComponent_${component.id}_files`; localStorage.setItem(backupKey, JSON.stringify(uploadedFiles)); // 임시 파일 삭제 const tempBackupKey = `fileComponent_${component.id}_files_temp`; localStorage.removeItem(tempBackupKey); console.log("💾 파일 저장 완료:", { componentId: component.id, fileCount: uploadedFiles.length, timestamp: timestamp, files: uploadedFiles.map(f => ({ objid: f.objid, name: f.realFileName })) }); toast.success(`${uploadedFiles.length}개 파일이 영구 저장되었습니다.`); } catch (error) { console.error('파일 저장 오류:', error); toast.error('파일 저장에 실패했습니다.'); } }, [uploadedFiles, onUpdateProperty, component.id, setGlobalFileState]); // 드래그앤드롭 이벤트 핸들러 const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setDragOver(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setDragOver(false); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const files = e.dataTransfer.files; if (files.length > 0) { handleFileUpload(files); } }, [handleFileUpload]); const handleFileSelect = useCallback((e: React.ChangeEvent) => { const files = e.target.files; if (files && files.length > 0) { handleFileUpload(files); } e.target.value = ''; }, [handleFileUpload]); // 컴포넌트 변경 시 로컬 상태 동기화 useEffect(() => { setLocalInputs({ docType: component.fileConfig?.docType || "DOCUMENT", docTypeName: component.fileConfig?.docTypeName || "일반 문서", dragDropText: component.fileConfig?.dragDropText || "파일을 드래그하거나 클릭하여 업로드하세요", maxSize: component.fileConfig?.maxSize || 10, maxFiles: component.fileConfig?.maxFiles || 5, newAcceptType: "", linkedTable: component.fileConfig?.linkedTable || "", linkedField: component.fileConfig?.linkedField || "", }); setLocalValues({ multiple: component.fileConfig?.multiple ?? true, showPreview: component.fileConfig?.showPreview ?? true, showProgress: component.fileConfig?.showProgress ?? true, autoLink: component.fileConfig?.autoLink ?? false, }); setAcceptTypes(component.fileConfig?.accept || []); // 파일 목록 동기화 - 컴포넌트 ID가 변경되었을 때만 초기화 const componentFiles = component.uploadedFiles || []; if (prevComponentIdRef.current !== component.id) { // 새로운 컴포넌트로 변경된 경우 console.log("🔄 FileComponentConfigPanel 새 컴포넌트 선택:", { prevComponentId: prevComponentIdRef.current, newComponentId: component.id, componentFiles: componentFiles.length, action: "새 컴포넌트 → 상태 초기화", globalFileStateExists: !!getGlobalFileState()[component.id], globalFileStateLength: getGlobalFileState()[component.id]?.length || 0, localStorageExists: !!localStorage.getItem(`fileComponent_${component.id}_files`), onUpdatePropertyExists: typeof onUpdateProperty === 'function' }); // 1순위: 전역 상태에서 파일 복원 const globalFileState = getGlobalFileState(); const globalFiles = globalFileState[component.id]; if (globalFiles && globalFiles.length > 0) { console.log("🌐 전역 상태에서 파일 복원:", { componentId: component.id, globalFiles: globalFiles.length, action: "전역 상태 → 상태 복원" }); setUploadedFiles(globalFiles); onUpdateProperty(component.id, "uploadedFiles", globalFiles); } // 2순위: localStorage에서 백업 파일 복원 else { const backupKey = `fileComponent_${component.id}_files`; const backupFiles = localStorage.getItem(backupKey); if (backupFiles && componentFiles.length === 0) { try { const parsedBackupFiles = JSON.parse(backupFiles); console.log("📂 localStorage에서 파일 복원:", { componentId: component.id, backupFiles: parsedBackupFiles.length, action: "백업 → 상태 복원" }); setUploadedFiles(parsedBackupFiles); // 전역 상태에도 저장 setGlobalFileState(prev => ({ ...prev, [component.id]: parsedBackupFiles })); // 컴포넌트 속성에도 복원 onUpdateProperty(component.id, "uploadedFiles", parsedBackupFiles); } catch (error) { console.error("백업 파일 복원 실패:", error); setUploadedFiles(componentFiles); } } else { setUploadedFiles(componentFiles); } } prevComponentIdRef.current = component.id; } else if (componentFiles.length > 0 && JSON.stringify(componentFiles) !== JSON.stringify(uploadedFiles)) { // 같은 컴포넌트에서 파일이 업데이트된 경우 console.log("🔄 FileComponentConfigPanel 파일 동기화:", { componentId: component.id, componentFiles: componentFiles.length, currentFiles: uploadedFiles.length, action: "컴포넌트 → 상태 동기화" }); setUploadedFiles(componentFiles); } }, [component.id]); // 컴포넌트 ID가 변경될 때만 초기화 // 전역 파일 상태 변경 감지 (화면 복원 포함) useEffect(() => { const handleGlobalFileStateChange = (event: CustomEvent) => { const { componentId, files, fileCount, isRestore } = event.detail; if (componentId === component.id) { console.log("🌐 FileComponentConfigPanel 전역 상태 변경 감지:", { componentId, fileCount, isRestore: !!isRestore, files: files?.map((f: any) => ({ objid: f.objid, name: f.realFileName })) }); if (files && Array.isArray(files)) { setUploadedFiles(files); if (isRestore) { console.log("✅ 파일 컴포넌트 설정 패널 데이터 복원 완료:", { componentId, restoredFileCount: files.length }); } } } }; if (typeof window !== 'undefined') { window.addEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); return () => { window.removeEventListener('globalFileStateChanged', handleGlobalFileStateChange as EventListener); }; } }, [component.id]); // 미리 정의된 문서 타입들 const docTypeOptions = [ { value: "CONTRACT", label: "계약서" }, { value: "DRAWING", label: "도면" }, { value: "PHOTO", label: "사진" }, { value: "DOCUMENT", label: "일반 문서" }, { value: "REPORT", label: "보고서" }, { value: "SPECIFICATION", label: "사양서" }, { value: "MANUAL", label: "매뉴얼" }, { value: "CERTIFICATE", label: "인증서" }, { value: "OTHER", label: "기타" }, ]; // 미리 정의된 파일 타입들 const commonFileTypes = [ { value: "image/*", label: "이미지" }, { value: ".pdf", label: "PDF" }, { value: ".doc,.docx", label: "Word" }, { value: ".xls,.xlsx", label: "Excel" }, { value: ".ppt,.pptx", label: "PowerPoint" }, { value: ".hwp,.hwpx,.hwpml", label: "한글" }, { value: ".hcdt", label: "한셀" }, { value: ".hpt", label: "한쇼" }, { value: ".pages", label: "Pages" }, { value: ".numbers", label: "Numbers" }, { value: ".keynote", label: "Keynote" }, { value: ".txt,.md,.rtf", label: "텍스트" }, { value: "video/*", label: "비디오" }, { value: "audio/*", label: "오디오" }, { value: ".zip,.rar,.7z", label: "압축파일" }, ]; // 파일 타입 추가 const addCommonFileType = useCallback((fileType: string) => { const types = fileType.split(','); const newTypes = [...acceptTypes]; types.forEach(type => { if (!newTypes.includes(type.trim())) { newTypes.push(type.trim()); } }); setAcceptTypes(newTypes); onUpdateProperty(component.id, "fileConfig.accept", newTypes); }, [acceptTypes, component.id, onUpdateProperty]); // 파일 타입 제거 const removeAcceptType = useCallback((typeToRemove: string) => { const newTypes = acceptTypes.filter(type => type !== typeToRemove); setAcceptTypes(newTypes); onUpdateProperty(component.id, "fileConfig.accept", newTypes); }, [acceptTypes, component.id, onUpdateProperty]); return (
{/* 기본 정보 */}

기본 설정

{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, docTypeName: newValue })); onUpdateProperty(component.id, "fileConfig.docTypeName", newValue); }} />
{/* 파일 업로드 제한 설정 */}

업로드 제한

{ const newValue = parseInt(e.target.value) || 10; setLocalInputs((prev) => ({ ...prev, maxSize: newValue })); onUpdateProperty(component.id, "fileConfig.maxSize", newValue); }} />
{ const newValue = parseInt(e.target.value) || 5; setLocalInputs((prev) => ({ ...prev, maxFiles: newValue })); onUpdateProperty(component.id, "fileConfig.maxFiles", newValue); }} />
{ setLocalValues((prev) => ({ ...prev, multiple: checked as boolean })); onUpdateProperty(component.id, "fileConfig.multiple", checked); }} />
{/* 허용 파일 타입 설정 */}

허용 파일 타입

{acceptTypes.map((type, index) => ( {type} ))} {acceptTypes.length === 0 && 모든 파일 타입 허용}
{commonFileTypes.map((fileType) => ( ))}
{/* 파일 업로드 영역 */}

파일 업로드

!uploading && document.getElementById('file-input-config')?.click()} >
{uploading ? ( <>

업로드 중...

) : ( <>

파일 업로드

)}
{/* 업로드된 파일 목록 */} {uploadedFiles.length > 0 && (
총 {formatFileSize(uploadedFiles.reduce((sum, file) => sum + file.fileSize, 0))}
{uploadedFiles.map((file) => (
{getFileIcon(file.fileExt)}

{file.realFileName}

{formatFileSize(file.fileSize)} {file.fileExt.toUpperCase()}
))}
)}
{/* 저장 버튼 - 주석처리 (파일이 자동으로 유지됨) */} {/* {uploadedFiles.length > 0 && (
{uploadedFiles.length}개 파일이 임시 저장됨

다른 컴포넌트로 이동하기 전에 파일을 저장해주세요.

)} */}
); };