class AdminFileManager { constructor() { this.files = []; this.categories = []; this.currentPage = 1; this.itemsPerPage = 10; this.filteredFiles = []; this.currentEditId = null; this.currentEditCategoryId = null; this.currentUser = null; this.isLoggedIn = false; this.init(); } async init() { console.log('๐Ÿ” Admin FileManager ์ดˆ๊ธฐํ™” ์‹œ์ž‘'); try { this.bindEvents(); await this.checkSession(); this.updateUI(); } catch (error) { console.error('์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜:', error); this.showNotification('์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } bindEvents() { // ๋กœ๊ทธ์ธ ์ด๋ฒคํŠธ const loginBtn = document.getElementById('loginBtn'); const adminPassword = document.getElementById('adminPassword'); if (loginBtn) { loginBtn.addEventListener('click', () => this.handleLogin()); } if (adminPassword) { adminPassword.addEventListener('keyup', (e) => { if (e.key === 'Enter') this.handleLogin(); }); } // ๋กœ๊ทธ์•„์›ƒ ์ด๋ฒคํŠธ const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => this.handleLogout()); } // ๊ฒ€์ƒ‰ ๋ฐ ์ •๋ ฌ ์ด๋ฒคํŠธ const searchBtn = document.getElementById('searchBtn'); const searchInput = document.getElementById('searchInput'); const categoryFilter = document.getElementById('categoryFilter'); const sortBy = document.getElementById('sortBy'); if (searchBtn) searchBtn.addEventListener('click', () => this.handleSearch()); if (searchInput) { searchInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') this.handleSearch(); }); } if (categoryFilter) categoryFilter.addEventListener('change', () => this.handleSearch()); if (sortBy) sortBy.addEventListener('change', () => this.handleSearch()); // ํƒญ ์ „ํ™˜ ์ด๋ฒคํŠธ this.bindTabEvents(); // ํŒŒ์ผ ๊ด€๋ฆฌ ์ด๋ฒคํŠธ this.bindFileEvents(); // ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ ์ด๋ฒคํŠธ this.bindCategoryEvents(); // ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ด๋ฒคํŠธ this.bindPaginationEvents(); } bindTabEvents() { const fileTabBtn = document.getElementById('fileTabBtn'); const categoryTabBtn = document.getElementById('categoryTabBtn'); const fileTab = document.getElementById('fileTab'); const categoryTab = document.getElementById('categoryTab'); if (fileTabBtn && categoryTabBtn && fileTab && categoryTab) { fileTabBtn.addEventListener('click', () => { // ํƒญ ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์ƒํƒœ ๋ณ€๊ฒฝ fileTabBtn.classList.add('active'); categoryTabBtn.classList.remove('active'); // ํƒญ ์ปจํ…์ธ  ํ‘œ์‹œ/์ˆจ๊น€ fileTab.classList.add('active'); categoryTab.classList.remove('active'); }); categoryTabBtn.addEventListener('click', () => { // ํƒญ ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์ƒํƒœ ๋ณ€๊ฒฝ categoryTabBtn.classList.add('active'); fileTabBtn.classList.remove('active'); // ํƒญ ์ปจํ…์ธ  ํ‘œ์‹œ/์ˆจ๊น€ categoryTab.classList.add('active'); fileTab.classList.remove('active'); // ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋ Œ๋”๋ง this.renderCategoryList(); }); } } bindCategoryEvents() { // ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ํผ const categoryForm = document.getElementById('categoryForm'); if (categoryForm) { categoryForm.addEventListener('submit', (e) => { e.preventDefault(); this.handleAddCategory(); }); } // ์นดํ…Œ๊ณ ๋ฆฌ ์ทจ์†Œ ๋ฒ„ํŠผ const cancelCategoryBtn = document.getElementById('cancelCategoryBtn'); if (cancelCategoryBtn) { cancelCategoryBtn.addEventListener('click', () => this.resetCategoryForm()); } // ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ this.bindModalEvents(); } bindModalEvents() { // ์ˆ˜์ • ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ const closeModal = document.getElementById('closeModal'); if (closeModal) { closeModal.addEventListener('click', () => { document.getElementById('editModal').style.display = 'none'; }); } // ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ • ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ const closeCategoryModal = document.getElementById('closeCategoryModal'); if (closeCategoryModal) { closeCategoryModal.addEventListener('click', () => { document.getElementById('editCategoryModal').style.display = 'none'; }); } // ์ˆ˜์ • ํผ ์ œ์ถœ const editForm = document.getElementById('editForm'); if (editForm) { editForm.addEventListener('submit', (e) => { e.preventDefault(); this.handleUpdateFile(); }); } // ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ • ํผ ์ œ์ถœ const editCategoryForm = document.getElementById('editCategoryForm'); if (editCategoryForm) { editCategoryForm.addEventListener('submit', (e) => { e.preventDefault(); this.handleUpdateCategory(); }); } } bindFileEvents() { // ํŒŒ์ผ ์ถ”๊ฐ€ ํผ const fileForm = document.getElementById('fileForm'); if (fileForm) { fileForm.addEventListener('submit', (e) => { e.preventDefault(); this.handleAddFile(); }); } // ์ทจ์†Œ ๋ฒ„ํŠผ const cancelBtn = document.getElementById('cancelBtn'); if (cancelBtn) { cancelBtn.addEventListener('click', () => this.resetForm()); } // ํŒŒ์ผ ์—…๋กœ๋“œ ์˜์—ญ this.setupFileUpload(); } bindEditModalEvents() { console.log('bindEditModalEvents ํ˜ธ์ถœ๋จ, ์ด๋ฏธ ๋ฐ”์ธ๋”ฉ๋œ ์ƒํƒœ:', this.editModalEventsBound); // ์ด๋ฒคํŠธ๊ฐ€ ์ด๋ฏธ ๋ฐ”์ธ๋”ฉ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ if (this.editModalEventsBound) { console.log('์ด๋ฏธ ๋ฐ”์ธ๋”ฉ๋จ, ์Šคํ‚ต'); return; } // ๊ธฐ์กด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ (์ค‘๋ณต ๋ฐฉ์ง€) const editForm = document.getElementById('editForm'); if (editForm && this.editFormHandler) { editForm.removeEventListener('submit', this.editFormHandler); } // ์ˆ˜์ • ํผ ์ œ์ถœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ ๋ฐ ๋ฐ”์ธ๋”ฉ this.editFormHandler = (e) => { e.preventDefault(); console.log('editForm ์ œ์ถœ๋จ'); // ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€ if (this.isUpdating) { console.log('์ด๋ฏธ ์—…๋ฐ์ดํŠธ ์ค‘์ž…๋‹ˆ๋‹ค.'); return; } this.handleUpdateFile(); }; if (editForm) { console.log('editForm ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ'); editForm.addEventListener('submit', this.editFormHandler); } // ์ˆ˜์ • ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ const closeModal = document.getElementById('closeModal'); if (closeModal) { console.log('closeModal ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ'); closeModal.addEventListener('click', () => { console.log('closeModal ํด๋ฆญ๋จ'); document.getElementById('editModal').style.display = 'none'; this.currentEditId = null; this.filesToDelete = []; // ์ƒˆ ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ดˆ๊ธฐํ™” this.updateNewFilesPreview([]); const newAttachmentsInput = document.getElementById('newAttachments'); if (newAttachmentsInput) { newAttachmentsInput.value = ''; } }); } // ํŒŒ์ผ ์„ ํƒ ๋ฒ„ํŠผ๊ณผ ๋“œ๋ž˜๊ทธ&๋“œ๋กญ ์˜์—ญ const fileSelectBtn = document.getElementById('fileSelectBtn'); const fileInput = document.getElementById('newAttachments'); const dropZone = document.getElementById('fileDropZone'); if (fileSelectBtn && fileInput && dropZone) { console.log('์ƒˆ๋กœ์šด ํŒŒ์ผ ์„ ํƒ ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ'); // ํŒŒ์ผ ์„ ํƒ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ fileSelectBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log('ํŒŒ์ผ ์„ ํƒ ๋ฒ„ํŠผ ํด๋ฆญ๋จ'); fileInput.click(); }); // ๋“œ๋กญ์กด ํด๋ฆญ ์ด๋ฒคํŠธ dropZone.addEventListener('click', (e) => { if (e.target === dropZone || e.target.closest('.drop-zone-content')) { fileInput.click(); } }); // ํŒŒ์ผ ์ž…๋ ฅ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ fileInput.addEventListener('change', (e) => { console.log('ํŒŒ์ผ ์„ ํƒ๋จ, ๊ฐœ์ˆ˜:', e.target.files.length); this.updateNewFilesPreview(e.target.files); }); // ๋“œ๋ž˜๊ทธ&๋“œ๋กญ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ this.bindDragAndDropEvents(dropZone, fileInput); } else { console.error('์ƒˆ๋กœ์šด ํŒŒ์ผ ์„ ํƒ ์š”์†Œ๋“ค์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ:', { fileSelectBtn: !!fileSelectBtn, fileInput: !!fileInput, dropZone: !!dropZone }); } // ๋ฐ”์ธ๋”ฉ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ ์„ค์ • this.editModalEventsBound = true; console.log('์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ ์™„๋ฃŒ'); } // ๋“œ๋ž˜๊ทธ&๋“œ๋กญ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ bindDragAndDropEvents(dropZone, fileInput) { console.log('๋“œ๋ž˜๊ทธ&๋“œ๋กญ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ ์‹œ์ž‘'); // ๋“œ๋ž˜๊ทธ ์ง„์ž… dropZone.addEventListener('dragenter', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('dragover'); }); // ๋“œ๋ž˜๊ทธ ์˜ค๋ฒ„ dropZone.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('dragover'); }); // ๋“œ๋ž˜๊ทธ ๋‚˜๊ฐ dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); // ์™„์ „ํžˆ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ๋งŒ ํด๋ž˜์Šค ์ œ๊ฑฐ if (!dropZone.contains(e.relatedTarget)) { dropZone.classList.remove('dragover'); } }); // ํŒŒ์ผ ๋“œ๋กญ dropZone.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('dragover'); const files = e.dataTransfer.files; console.log('ํŒŒ์ผ ๋“œ๋กญ๋จ, ๊ฐœ์ˆ˜:', files.length); if (files.length > 0) { // ํŒŒ์ผ ์ž…๋ ฅ์— ๋“œ๋กญ๋œ ํŒŒ์ผ๋“ค ์„ค์ • fileInput.files = files; this.updateNewFilesPreview(files); } }); console.log('๋“œ๋ž˜๊ทธ&๋“œ๋กญ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ ์™„๋ฃŒ'); } // ์ƒˆ๋กœ์šด ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์—…๋ฐ์ดํŠธ updateNewFilesPreview(files) { const previewContainer = document.getElementById('newFilesPreview'); if (!files || files.length === 0) { previewContainer.classList.remove('show'); previewContainer.innerHTML = ''; return; } previewContainer.classList.add('show'); previewContainer.innerHTML = ''; Array.from(files).forEach((file, index) => { const fileItem = document.createElement('div'); fileItem.className = 'preview-file-item'; const fileIcon = this.getFileIcon(file.name); const fileSize = this.formatFileSize(file.size); fileItem.innerHTML = `
${fileIcon}
${this.escapeHtml(file.name)}
${fileSize}
`; // ์ œ๊ฑฐ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ const removeBtn = fileItem.querySelector('.preview-file-remove'); removeBtn.addEventListener('click', () => { this.removeFileFromPreview(index); }); previewContainer.appendChild(fileItem); }); } // ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ์ œ๊ฑฐ removeFileFromPreview(indexToRemove) { const fileInput = document.getElementById('newAttachments'); const dt = new DataTransfer(); Array.from(fileInput.files).forEach((file, index) => { if (index !== indexToRemove) { dt.items.add(file); } }); fileInput.files = dt.files; this.updateNewFilesPreview(fileInput.files); } // ์ƒˆ ์ฒจ๋ถ€ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ‘œ์‹œ (๊ธฐ์กด ํ•จ์ˆ˜ - ํ˜ธํ™˜์„ฑ ์œ ์ง€) showAttachmentPreview(files) { const container = document.querySelector('.attachment-preview'); if (!container) { // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ const previewDiv = document.createElement('div'); previewDiv.className = 'attachment-preview'; document.querySelector('.new-attachment-section').appendChild(previewDiv); } const preview = document.querySelector('.attachment-preview'); if (files.length === 0) { preview.style.display = 'none'; return; } let previewText = `์„ ํƒ๋œ ํŒŒ์ผ (${files.length}๊ฐœ): `; const fileNames = Array.from(files).map(file => file.name).slice(0, 3); if (files.length > 3) { fileNames.push(`์™ธ ${files.length - 3}๊ฐœ`); } previewText += fileNames.join(', '); preview.innerHTML = previewText; preview.style.display = 'block'; } bindPaginationEvents() { const prevBtn = document.getElementById('prevPage'); const nextBtn = document.getElementById('nextPage'); if (prevBtn) prevBtn.addEventListener('click', () => this.goToPrevPage()); if (nextBtn) nextBtn.addEventListener('click', () => this.goToNextPage()); } setupFileUpload() { const fileUploadArea = document.getElementById('fileUploadArea'); const fileUpload = document.getElementById('fileUpload'); if (fileUploadArea && fileUpload) { // ํด๋ฆญ์œผ๋กœ ํŒŒ์ผ ์„ ํƒ fileUploadArea.addEventListener('click', () => { fileUpload.click(); }); // ํŒŒ์ผ ์„ ํƒ ์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ fileUpload.addEventListener('change', (e) => { this.handleFileSelection(e.target.files); }); // ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ fileUploadArea.addEventListener('dragover', (e) => { e.preventDefault(); fileUploadArea.classList.add('drag-over'); }); fileUploadArea.addEventListener('dragleave', () => { fileUploadArea.classList.remove('drag-over'); }); fileUploadArea.addEventListener('drop', (e) => { e.preventDefault(); fileUploadArea.classList.remove('drag-over'); this.handleFileSelection(e.dataTransfer.files); }); } } async checkSession() { try { const response = await fetch('/api/auth/session'); if (response.ok) { const data = await response.json(); if (data.user) { this.currentUser = data.user; this.isLoggedIn = true; await this.loadData(); } } } catch (error) { console.log('์„ธ์…˜ ํ™•์ธ ์‹คํŒจ:', error); } } async handleLogin() { const email = document.getElementById('adminEmail').value.trim(); const password = document.getElementById('adminPassword').value; const loginBtn = document.getElementById('loginBtn'); if (!email || !password) { this.showNotification('์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', 'error'); return; } try { loginBtn.disabled = true; loginBtn.textContent = '๋กœ๊ทธ์ธ ์ค‘...'; const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { this.currentUser = data.user; this.isLoggedIn = true; this.showNotification('๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!', 'success'); await this.loadData(); this.updateUI(); } else { throw new Error(data.message || '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } } catch (error) { console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error); this.showNotification(error.message || '๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } finally { loginBtn.disabled = false; loginBtn.textContent = '๋กœ๊ทธ์ธ'; } } async handleLogout() { try { await fetch('/api/auth/logout', { method: 'POST' }); this.currentUser = null; this.isLoggedIn = false; this.files = []; this.categories = []; this.showNotification('๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.', 'info'); this.updateUI(); // ํผ ์ดˆ๊ธฐํ™” document.getElementById('adminPassword').value = ''; } catch (error) { console.error('๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜:', error); this.showNotification('๋กœ๊ทธ์•„์›ƒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } async loadData() { if (!this.isLoggedIn) return; try { // ํŒŒ์ผ ๋ชฉ๋ก ๋กœ๋“œ const filesData = await window.AdminAPI.Files.getAll(); this.files = filesData.data || []; console.log('ํŒŒ์ผ ๋กœ๋“œ ์™„๋ฃŒ:', this.files.length, '๊ฐœ'); // ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋กœ๋“œ const categoriesData = await window.AdminAPI.Categories.getAll(); this.categories = categoriesData.data || []; console.log('์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ์™„๋ฃŒ:', this.categories.length, '๊ฐœ'); console.log('์นดํ…Œ๊ณ ๋ฆฌ ๋ฐ์ดํ„ฐ:', this.categories); this.filteredFiles = [...this.files]; this.renderFiles(); this.updatePagination(); this.updateCategoryOptions(); } catch (error) { console.error('๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜:', error); this.showNotification('๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } updateUI() { const loginSection = document.getElementById('loginSection'); const adminSection = document.getElementById('adminSection'); const adminPanel = document.getElementById('adminPanel'); const adminUserEmail = document.getElementById('adminUserEmail'); if (this.isLoggedIn) { // ๋กœ๊ทธ์ธ ์ƒํƒœ if (loginSection) loginSection.style.display = 'none'; if (adminSection) adminSection.style.display = 'flex'; if (adminPanel) adminPanel.style.display = 'block'; if (adminUserEmail) adminUserEmail.textContent = this.currentUser.email; } else { // ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ if (loginSection) loginSection.style.display = 'flex'; if (adminSection) adminSection.style.display = 'none'; if (adminPanel) adminPanel.style.display = 'none'; } } updateCategoryOptions() { const categorySelects = ['fileCategory', 'categoryFilter', 'editCategory']; categorySelects.forEach(selectId => { const select = document.getElementById(selectId); if (!select) return; // ๊ธฐ์กด ์˜ต์…˜ ์ œ๊ฑฐ (์ฒซ ๋ฒˆ์งธ ์˜ต์…˜ ์ œ์™ธ) while (select.children.length > 1) { select.removeChild(select.lastChild); } // ์นดํ…Œ๊ณ ๋ฆฌ ์˜ต์…˜ ์ถ”๊ฐ€ this.categories.forEach(category => { const option = document.createElement('option'); option.value = category.name; option.textContent = category.name; select.appendChild(option); }); }); } handleSearch() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const categoryFilter = document.getElementById('categoryFilter').value; let filteredFiles = this.files; if (searchTerm) { filteredFiles = filteredFiles.filter(file => file.title.toLowerCase().includes(searchTerm) || file.description.toLowerCase().includes(searchTerm) || (file.tags && this.parseJsonTags(file.tags).some(tag => tag.toLowerCase().includes(searchTerm))) ); } if (categoryFilter) { filteredFiles = filteredFiles.filter(file => file.category === categoryFilter); } this.filteredFiles = filteredFiles; this.currentPage = 1; this.renderFiles(); this.updatePagination(); } parseJsonTags(tags) { try { if (typeof tags === 'string') { return JSON.parse(tags); } return Array.isArray(tags) ? tags : []; } catch (error) { return []; } } renderFiles() { const fileList = document.getElementById('fileList'); const sortBy = document.getElementById('sortBy').value; if (!fileList) return; // ์ •๋ ฌ const sortedFiles = [...this.filteredFiles].sort((a, b) => { switch (sortBy) { case 'title': return a.title.localeCompare(b.title); case 'category': return a.category.localeCompare(b.category); case 'date': default: return new Date(b.created_at) - new Date(a.created_at); } }); // ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ ์šฉ const startIndex = (this.currentPage - 1) * this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage; const paginatedFiles = sortedFiles.slice(startIndex, endIndex); if (sortedFiles.length === 0) { fileList.innerHTML = ` ๐Ÿ“‚ ์กฐ๊ฑด์— ๋งž๋Š” ์ž๋ฃŒ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. `; return; } fileList.innerHTML = paginatedFiles.map((file, index) => this.createFileRowHTML(file, startIndex + index + 1) ).join(''); } createFileRowHTML(file, rowNumber) { const createdDate = new Date(file.created_at).toLocaleDateString('ko-KR'); const hasAttachments = file.files && file.files.length > 0; const tags = this.parseJsonTags(file.tags); return ` ${rowNumber} ${file.category}
${this.escapeHtml(file.title)} ${file.description ? `
${this.escapeHtml(file.description)}` : ''} ${tags.length > 0 ? `
${tags.map(tag => `#${this.escapeHtml(tag)}`).join('')}
` : '' }
${hasAttachments ? `
${file.files.map((f, index) => `
${this.getFileIcon(f.original_name || 'unknown')} ${this.escapeHtml(f.original_name || 'ํŒŒ์ผ')}
` ).join('')}
` : `-` } ${createdDate} ${hasAttachments ? `` : '' } `; } async handleAddFile() { if (!this.isLoggedIn) { this.showNotification('๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', 'error'); return; } const title = document.getElementById('fileTitle').value.trim(); const description = document.getElementById('fileDescription').value.trim(); const category = document.getElementById('fileCategory').value; const tags = document.getElementById('fileTags').value.trim(); const fileInput = document.getElementById('fileUpload'); if (!title || !category) { this.showNotification('์ œ๋ชฉ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.', 'error'); return; } try { // ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ this.showLoadingState('ํŒŒ์ผ ๋“ฑ๋ก ์ค‘...', true); const submitBtn = document.getElementById('submitBtn'); submitBtn.disabled = true; submitBtn.textContent = '๋“ฑ๋ก ์ค‘...'; const formData = new FormData(); formData.append('title', title); formData.append('description', description); formData.append('category', category); formData.append('tags', JSON.stringify(tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [])); // ํŒŒ์ผ ์ถ”๊ฐ€ if (fileInput.files.length > 0) { for (const file of fileInput.files) { formData.append('files', file); } } const response = await fetch('/api/files', { method: 'POST', body: formData }); const data = await response.json(); if (response.ok) { this.showNotification('ํŒŒ์ผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค!', 'success'); this.resetForm(); await this.loadData(); } else { throw new Error(data.message || 'ํŒŒ์ผ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } } catch (error) { console.error('ํŒŒ์ผ ์ถ”๊ฐ€ ์˜ค๋ฅ˜:', error); this.showNotification(error.message || 'ํŒŒ์ผ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } finally { // ๋กœ๋”ฉ ์ƒํƒœ ํ•ด์ œ ๋ฐ ๋ฒ„ํŠผ ๋ณต์› this.hideLoadingState(); const submitBtn = document.getElementById('submitBtn'); submitBtn.disabled = false; submitBtn.textContent = '๐Ÿ“ค ์ถ”๊ฐ€'; } } async deleteFile(id) { if (!confirm('์ •๋ง๋กœ ์ด ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) { return; } try { const response = await fetch(`/api/files/${id}`, { method: 'DELETE' }); if (response.ok) { this.showNotification('ํŒŒ์ผ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.', 'success'); await this.loadData(); } else { const data = await response.json(); throw new Error(data.message || 'ํŒŒ์ผ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } } catch (error) { console.error('ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜:', error); this.showNotification(error.message || 'ํŒŒ์ผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } editFile(id) { const file = this.files.find(f => f.id === id); if (!file) { this.showNotification('ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } // ์ˆ˜์ • ๋ชจ๋‹ฌ์— ๋ฐ์ดํ„ฐ ์ฑ„์šฐ๊ธฐ document.getElementById('editTitle').value = file.title; document.getElementById('editDescription').value = file.description || ''; document.getElementById('editCategory').value = file.category; const tags = this.parseJsonTags(file.tags); document.getElementById('editTags').value = tags.join(', '); // ๊ธฐ์กด ์ฒจ๋ถ€ํŒŒ์ผ ํ‘œ์‹œ this.renderExistingAttachments(file.files || []); // ์ฒจ๋ถ€ํŒŒ์ผ ์‚ญ์ œ ๋ชฉ๋ก ์ดˆ๊ธฐํ™” this.filesToDelete = []; // ์ƒˆ ํŒŒ์ผ ์ž…๋ ฅ ์ดˆ๊ธฐํ™” const newAttachmentsInput = document.getElementById('newAttachments'); if (newAttachmentsInput) { newAttachmentsInput.value = ''; } // ์ƒˆ ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ดˆ๊ธฐํ™” this.updateNewFilesPreview([]); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ (ํ•œ๋ฒˆ๋งŒ) if (!this.editModalEventsBound) { this.bindEditModalEvents(); } this.currentEditId = id; document.getElementById('editModal').style.display = 'flex'; } // ๊ธฐ์กด ์ฒจ๋ถ€ํŒŒ์ผ ๋ Œ๋”๋ง renderExistingAttachments(attachments) { const container = document.getElementById('existingAttachments'); const noFilesIndicator = document.getElementById('noExistingFiles'); if (!attachments || attachments.length === 0) { noFilesIndicator.style.display = 'block'; // ๊ธฐ์กด ํŒŒ์ผ ์•„์ดํ…œ๋“ค ์ œ๊ฑฐ const existingItems = container.querySelectorAll('.attachment-item'); existingItems.forEach(item => item.remove()); return; } noFilesIndicator.style.display = 'none'; // ๊ธฐ์กด ์•„์ดํ…œ๋“ค ์ œ๊ฑฐ const existingItems = container.querySelectorAll('.attachment-item'); existingItems.forEach(item => item.remove()); // ์ƒˆ๋กœ์šด ์ฒจ๋ถ€ํŒŒ์ผ ์•„์ดํ…œ๋“ค ์ถ”๊ฐ€ attachments.forEach((file, index) => { const fileIcon = this.getFileIcon(file.original_name); const fileSize = this.formatFileSize(file.file_size); const attachmentItem = document.createElement('div'); attachmentItem.className = 'attachment-item'; attachmentItem.setAttribute('data-attachment-id', file.id); attachmentItem.innerHTML = `
${fileIcon}
${this.escapeHtml(file.original_name)}
${fileSize}
`; container.appendChild(attachmentItem); }); } // ์ฒจ๋ถ€ํŒŒ์ผ ์‚ญ์ œ ํ‘œ์‹œ markAttachmentForDeletion(attachmentId, buttonElement) { const attachmentItem = buttonElement.closest('.attachment-item'); if (!this.filesToDelete) { this.filesToDelete = []; } if (this.filesToDelete.includes(attachmentId)) { // ์‚ญ์ œ ์ทจ์†Œ this.filesToDelete = this.filesToDelete.filter(id => id !== attachmentId); attachmentItem.style.opacity = '1'; attachmentItem.style.textDecoration = 'none'; buttonElement.innerHTML = '๐Ÿ—‘๏ธ ์‚ญ์ œ'; buttonElement.style.background = '#ef4444'; } else { // ์‚ญ์ œ ํ‘œ์‹œ this.filesToDelete.push(attachmentId); attachmentItem.style.opacity = '0.5'; attachmentItem.style.textDecoration = 'line-through'; buttonElement.innerHTML = 'โ†ถ ์ทจ์†Œ'; buttonElement.style.background = '#6b7280'; } } // ํŒŒ์ผ ํฌ๊ธฐ ํฌ๋งทํŒ… formatFileSize(bytes) { if (!bytes) return '0 B'; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; } async handleUpdateFile() { if (!this.currentEditId) { this.showNotification('์ˆ˜์ •ํ•  ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } // ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€ ํ”Œ๋ž˜๊ทธ ์„ค์ • if (this.isUpdating) { console.log('์ด๋ฏธ ์—…๋ฐ์ดํŠธ ์ค‘์ž…๋‹ˆ๋‹ค.'); return; } this.isUpdating = true; const title = document.getElementById('editTitle').value.trim(); const description = document.getElementById('editDescription').value.trim(); const category = document.getElementById('editCategory').value; const tags = document.getElementById('editTags').value.trim(); if (!title || !category) { this.isUpdating = false; // ํ”Œ๋ž˜๊ทธ ํ•ด์ œ this.showNotification('์ œ๋ชฉ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.', 'error'); return; } try { // ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ ์‹œ์ž‘ this.showLoadingState('์ˆ˜์ • ์ค‘...', true); // FormData๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ „์†ก const formData = new FormData(); formData.append('title', title); formData.append('description', description); formData.append('category', category); formData.append('tags', JSON.stringify(tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [])); // ์‚ญ์ œํ•  ์ฒจ๋ถ€ํŒŒ์ผ ID๋“ค if (this.filesToDelete && this.filesToDelete.length > 0) { formData.append('filesToDelete', JSON.stringify(this.filesToDelete)); } // ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•  ์ฒจ๋ถ€ํŒŒ์ผ๋“ค const newFileInput = document.getElementById('newAttachments'); if (newFileInput && newFileInput.files.length > 0) { for (const file of newFileInput.files) { formData.append('files', file); } } // API ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์—…๋ฐ์ดํŠธ const data = await window.AdminAPI.Files.update(this.currentEditId, formData); this.showNotification('ํŒŒ์ผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค!', 'success'); document.getElementById('editModal').style.display = 'none'; this.currentEditId = null; this.filesToDelete = []; // ์ƒˆ ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ดˆ๊ธฐํ™” this.updateNewFilesPreview([]); const newAttachmentsInput = document.getElementById('newAttachments'); if (newAttachmentsInput) { newAttachmentsInput.value = ''; } await this.loadData(); } catch (error) { console.error('ํŒŒ์ผ ์ˆ˜์ • ์˜ค๋ฅ˜:', error); this.showNotification(error.message || 'ํŒŒ์ผ ์ˆ˜์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } finally { // ์ž‘์—… ์™„๋ฃŒ ํ›„ ํ”Œ๋ž˜๊ทธ ํ•ด์ œ ๋ฐ ๋กœ๋”ฉ ์ƒํƒœ ์ œ๊ฑฐ this.isUpdating = false; this.hideLoadingState(); } } async downloadFiles(id) { console.log('downloadFiles ํ˜ธ์ถœ๋จ:', id); const file = this.files.find(f => f.id === id); console.log('์ฐพ์€ ํŒŒ์ผ:', file); if (!file || !file.files || file.files.length === 0) { console.log('์ฒจ๋ถ€ํŒŒ์ผ ์—†์Œ'); this.showNotification('์ฒจ๋ถ€ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } try { console.log('๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘, ํŒŒ์ผ ๊ฐœ์ˆ˜:', file.files.length); if (file.files.length === 1) { // ๋‹จ์ผ ํŒŒ์ผ: ์ง์ ‘ ๋‹ค์šด๋กœ๋“œ console.log('๋‹จ์ผ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ'); await this.downloadSingleFile(id, 0); this.showNotification('ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ', 'success'); } else { // ๋‹ค์ค‘ ํŒŒ์ผ: ๊ฐ๊ฐ ๋‹ค์šด๋กœ๋“œ console.log('๋‹ค์ค‘ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ'); for (let i = 0; i < file.files.length; i++) { console.log(`ํŒŒ์ผ ${i + 1}/${file.files.length} ๋‹ค์šด๋กœ๋“œ ์ค‘`); await this.downloadSingleFile(id, i); // ์งง์€ ๋”œ๋ ˆ์ด๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•  ์‹œ๊ฐ„์„ ์คŒ await new Promise(resolve => setTimeout(resolve, 500)); } this.showNotification(`${file.files.length}๊ฐœ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ`, 'success'); } } catch (error) { console.error('ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์˜ค๋ฅ˜:', error); this.showNotification(`๋‹ค์šด๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, 'error'); } } async downloadSingleFile(fileId, attachmentIndex) { try { // ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘ ๋กœ๋”ฉ ํ‘œ์‹œ this.showLoadingState('๋‹ค์šด๋กœ๋“œ ์ค€๋น„ ์ค‘...', false); console.log('downloadSingleFile ํ˜ธ์ถœ๋จ:', fileId, attachmentIndex); const file = this.files.find(f => f.id === fileId); console.log('์ฐพ์€ ํŒŒ์ผ:', file); if (!file || !file.files[attachmentIndex]) { console.log('ํŒŒ์ผ ๋˜๋Š” ์ฒจ๋ถ€ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ'); throw new Error('ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.'); } const attachmentId = file.files[attachmentIndex].id; const downloadUrl = `/api/download/${fileId}/${attachmentId}`; console.log('๋‹ค์šด๋กœ๋“œ URL:', downloadUrl); const response = await fetch(downloadUrl, { credentials: 'include' }); console.log('์‘๋‹ต ์ƒํƒœ:', response.status, response.statusText); if (!response.ok) { const errorText = await response.text(); console.log('์‘๋‹ต ์˜ค๋ฅ˜:', errorText); throw new Error(`HTTP error! status: ${response.status} - ${errorText}`); } console.log('๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘...'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); // ํŒŒ์ผ๋ช…์„ ์„œ๋ฒ„์—์„œ ์ „์†ก๋œ ์ •๋ณด์—์„œ ์ถ”์ถœ (๊ฐœ์„ ๋œ ๋ฐฉ์‹) const contentDisposition = response.headers.get('Content-Disposition'); let filename = file.files[attachmentIndex].original_name || `download_${Date.now()}`; console.log('๐Ÿ“ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ๋ช… ์ฒ˜๋ฆฌ:', { original_name: file.files[attachmentIndex].original_name, content_disposition: contentDisposition, default_filename: filename }); if (contentDisposition) { // RFC 5987 filename* ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์šฐ์„  ์ฒ˜๋ฆฌ (UTF-8 ์ง€์›) const filenameStarMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/); if (filenameStarMatch) { filename = decodeURIComponent(filenameStarMatch[1]); console.log('๐Ÿ“ UTF-8 ํŒŒ์ผ๋ช… ์ถ”์ถœ:', filename); } else { // ์ผ๋ฐ˜ filename ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ const filenameMatch = contentDisposition.match(/filename="?([^";\r\n]+)"?/); if (filenameMatch) { filename = filenameMatch[1]; console.log('๐Ÿ“ ๊ธฐ๋ณธ ํŒŒ์ผ๋ช… ์ถ”์ถœ:', filename); } } } // ํŒŒ์ผ๋ช…์ด ์—ฌ์ „ํžˆ ๋น„์–ด์žˆ๋‹ค๋ฉด ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ if (!filename || filename.trim() === '') { filename = file.files[attachmentIndex].original_name || `download_${Date.now()}`; console.log('๐Ÿ“ ๊ธฐ๋ณธ ํŒŒ์ผ๋ช… ์‚ฌ์šฉ:', filename); } const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); console.log('๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ'); this.hideLoadingState(); } catch (error) { console.error('downloadSingleFile ์˜ค๋ฅ˜:', error); this.hideLoadingState(); throw error; } } handleFileSelection(files) { const selectedFiles = document.getElementById('selectedFiles'); if (!selectedFiles) return; selectedFiles.innerHTML = ''; Array.from(files).forEach(file => { const fileItem = document.createElement('div'); fileItem.className = 'selected-file-item'; fileItem.innerHTML = ` ${this.getFileIcon(file.name)} ${this.escapeHtml(file.name)} ${this.formatFileSize(file.size)} `; selectedFiles.appendChild(fileItem); }); } getFileIcon(fileName) { const ext = fileName.split('.').pop().toLowerCase(); const iconMap = { 'pdf': '๐Ÿ“„', 'doc': '๐Ÿ“', 'docx': '๐Ÿ“', 'xls': '๐Ÿ“Š', 'xlsx': '๐Ÿ“Š', 'ppt': '๐Ÿ“ฝ๏ธ', 'pptx': '๐Ÿ“ฝ๏ธ', 'jpg': '๐Ÿ–ผ๏ธ', 'jpeg': '๐Ÿ–ผ๏ธ', 'png': '๐Ÿ–ผ๏ธ', 'gif': '๐Ÿ–ผ๏ธ', 'mp4': '๐ŸŽฅ', 'avi': '๐ŸŽฅ', 'mov': '๐ŸŽฅ', 'mp3': '๐ŸŽต', 'wav': '๐ŸŽต', 'zip': '๐Ÿ“ฆ', 'rar': '๐Ÿ“ฆ', 'txt': '๐Ÿ“„' }; return iconMap[ext] || '๐Ÿ“„'; } formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } resetForm() { document.getElementById('fileTitle').value = ''; document.getElementById('fileDescription').value = ''; document.getElementById('fileCategory').value = ''; document.getElementById('fileTags').value = ''; document.getElementById('fileUpload').value = ''; const selectedFiles = document.getElementById('selectedFiles'); if (selectedFiles) selectedFiles.innerHTML = ''; } updatePagination() { const totalPages = Math.max(1, Math.ceil(this.filteredFiles.length / this.itemsPerPage)); const pagination = document.getElementById('pagination'); const prevBtn = document.getElementById('prevPage'); const nextBtn = document.getElementById('nextPage'); const pageInfo = document.getElementById('pageInfo'); if (!pagination) return; pagination.style.display = 'flex'; if (prevBtn) prevBtn.disabled = this.currentPage <= 1; if (nextBtn) nextBtn.disabled = this.currentPage >= totalPages || this.filteredFiles.length === 0; const displayTotalPages = this.filteredFiles.length === 0 ? 1 : totalPages; const displayCurrentPage = this.filteredFiles.length === 0 ? 1 : this.currentPage; if (pageInfo) pageInfo.textContent = `${displayCurrentPage} / ${displayTotalPages}`; } goToPrevPage() { if (this.currentPage > 1) { this.currentPage--; this.renderFiles(); this.updatePagination(); } } goToNextPage() { const totalPages = Math.ceil(this.filteredFiles.length / this.itemsPerPage); if (this.currentPage < totalPages) { this.currentPage++; this.renderFiles(); this.updatePagination(); } } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 8px; color: white; font-weight: 500; z-index: 10000; max-width: 400px; background: ${type === 'success' ? '#48bb78' : type === 'error' ? '#f56565' : '#4299e1'}; `; document.body.appendChild(notification); setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 3000); } showFileDetail(id) { const file = this.files.find(f => f.id === id); if (!file) { this.showNotification('ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } const tags = this.parseJsonTags(file.tags); const createdDate = new Date(file.created_at).toLocaleString('ko-KR'); const updatedDate = new Date(file.updated_at).toLocaleString('ko-KR'); const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); } async handleAddCategory() { const categoryName = document.getElementById('categoryName').value.trim(); if (!categoryName) { this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', 'error'); return; } // ์ค‘๋ณต ํ™•์ธ if (this.categories.some(cat => cat.name === categoryName)) { this.showNotification('์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ์ž…๋‹ˆ๋‹ค.', 'error'); return; } try { const data = await window.AdminAPI.Categories.create(categoryName); this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค!', 'success'); this.resetCategoryForm(); await this.loadData(); this.renderCategoryList(); } catch (error) { console.error('์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ์˜ค๋ฅ˜:', error); this.showNotification(error.message || '์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } resetCategoryForm() { document.getElementById('categoryName').value = ''; } renderCategoryList() { console.log('renderCategoryList ํ˜ธ์ถœ๋จ'); console.log('ํ˜„์žฌ ์นดํ…Œ๊ณ ๋ฆฌ ๋ฐ์ดํ„ฐ:', this.categories); const categoryList = document.getElementById('categoryList'); if (!categoryList) { console.error('categoryList ์š”์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค'); return; } if (this.categories.length === 0) { console.log('์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†์–ด์„œ ๋นˆ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ'); categoryList.innerHTML = '

๋“ฑ๋ก๋œ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

'; return; } console.log('์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋ Œ๋”๋ง ์‹œ์ž‘'); categoryList.innerHTML = this.categories.map(category => { console.log('์นดํ…Œ๊ณ ๋ฆฌ ๋ Œ๋”๋ง:', category); return `
${this.escapeHtml(category.name)}
`; }).join(''); console.log('์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋ Œ๋”๋ง ์™„๋ฃŒ'); } editCategory(id) { console.log('editCategory ํ˜ธ์ถœ๋จ, ID:', id, 'Type:', typeof id); console.log('์ „์ฒด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก:', this.categories); const category = this.categories.find(c => { console.log('๋น„๊ต:', c.id, 'vs', id, 'Type:', typeof c.id, 'vs', typeof id); return c.id == id; // == ์‚ฌ์šฉ์œผ๋กœ ํƒ€์ž… ๋ณ€ํ™˜ ํ—ˆ์šฉ }); console.log('์ฐพ์€ ์นดํ…Œ๊ณ ๋ฆฌ:', category); if (!category) { console.error('์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ID:', id); this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } document.getElementById('editCategoryName').value = category.name; this.currentEditCategoryId = id; document.getElementById('editCategoryModal').style.display = 'flex'; } async handleUpdateCategory() { if (!this.currentEditCategoryId) { this.showNotification('์ˆ˜์ •ํ•  ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } const categoryName = document.getElementById('editCategoryName').value.trim(); if (!categoryName) { this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', 'error'); return; } // ์ค‘๋ณต ํ™•์ธ (์ž๊ธฐ ์ž์‹  ์ œ์™ธ) if (this.categories.some(cat => cat.name === categoryName && cat.id !== this.currentEditCategoryId)) { this.showNotification('์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ์ž…๋‹ˆ๋‹ค.', 'error'); return; } try { const data = await window.AdminAPI.Categories.update(this.currentEditCategoryId, categoryName); this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค!', 'success'); document.getElementById('editCategoryModal').style.display = 'none'; this.currentEditCategoryId = null; await this.loadData(); this.renderCategoryList(); } catch (error) { console.error('์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ • ์˜ค๋ฅ˜:', error); this.showNotification(error.message || '์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } async deleteCategory(id) { console.log('deleteCategory ํ˜ธ์ถœ๋จ, ID:', id, 'Type:', typeof id); console.log('์ „์ฒด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก:', this.categories); const category = this.categories.find(c => { console.log('๋น„๊ต:', c.id, 'vs', id, 'Type:', typeof c.id, 'vs', typeof id); return c.id == id; // == ์‚ฌ์šฉ์œผ๋กœ ํƒ€์ž… ๋ณ€ํ™˜ ํ—ˆ์šฉ }); console.log('์ฐพ์€ ์นดํ…Œ๊ณ ๋ฆฌ:', category); if (!category) { console.error('์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ID:', id); this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'error'); return; } // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ const filesUsingCategory = this.files.filter(file => file.category === category.name); if (filesUsingCategory.length > 0) { if (!confirm(`์ด ์นดํ…Œ๊ณ ๋ฆฌ๋Š” ${filesUsingCategory.length}๊ฐœ์˜ ํŒŒ์ผ์—์„œ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค.\n์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (์‚ฌ์šฉ ์ค‘์ธ ํŒŒ์ผ๋“ค์˜ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ '๊ธฐํƒ€'๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.)`)) { return; } } else { if (!confirm(`์ •๋ง๋กœ '${category.name}' ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`)) { return; } } try { await window.AdminAPI.Categories.delete(id); this.showNotification('์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.', 'success'); await this.loadData(); this.renderCategoryList(); } catch (error) { console.error('์นดํ…Œ๊ณ ๋ฆฌ ์‚ญ์ œ ์˜ค๋ฅ˜:', error); this.showNotification(error.message || '์นดํ…Œ๊ณ ๋ฆฌ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', 'error'); } } // ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ showLoadingState(message = '์ฒ˜๋ฆฌ ์ค‘...', disableForm = false) { console.log('๐Ÿ”„ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ:', message); // ๊ธฐ์กด ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์ œ๊ฑฐ this.hideLoadingState(); // ๋กœ๋”ฉ ์˜ค๋ฒ„๋ ˆ์ด ์ƒ์„ฑ const loadingOverlay = document.createElement('div'); loadingOverlay.id = 'loadingOverlay'; loadingOverlay.className = 'loading-overlay'; loadingOverlay.innerHTML = `
${message}
`; document.body.appendChild(loadingOverlay); // ํผ ๋น„ํ™œ์„ฑํ™” (์„ ํƒ์ ) if (disableForm) { const forms = document.querySelectorAll('form, button'); forms.forEach(element => { element.style.pointerEvents = 'none'; element.style.opacity = '0.6'; }); } } // ๋กœ๋”ฉ ์ƒํƒœ ์ˆจ๊น€ hideLoadingState() { console.log('โœ… ๋กœ๋”ฉ ์ƒํƒœ ํ•ด์ œ'); // ๋กœ๋”ฉ ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ const loadingOverlay = document.getElementById('loadingOverlay'); if (loadingOverlay) { loadingOverlay.remove(); } // ํผ ํ™œ์„ฑํ™” const forms = document.querySelectorAll('form, button'); forms.forEach(element => { element.style.pointerEvents = ''; element.style.opacity = ''; }); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // ์ „์—ญ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ let adminManager; document.addEventListener('DOMContentLoaded', () => { adminManager = new AdminFileManager(); window.adminManager = adminManager; // ์ „์—ญ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก });