From 122d0e2582f1b925cc4c1844964a72eb46dfb020 Mon Sep 17 00:00:00 2001 From: vibsin9322 Date: Tue, 19 Aug 2025 20:29:32 +0900 Subject: [PATCH] Enhance pagination and UI consistency across admin/user interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed pagination to display exactly 10 items per page - Made pagination controls always visible, even with empty data - Synchronized data structure and sorting between admin and main pages - Improved pagination styling with better visibility and centering - Enhanced attachment display with file icons, names, and sizes - Implemented detailed view pages for both interfaces - Optimized table row spacing for more compact display - Centered attachment icons with file names for better visual balance πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- admin/script.js | 269 ++++++++++++++++++++++----------- admin/styles.css | 364 +++++++++++++++++++++++++++++++++++++++++++-- script.js | 179 ++++++++++++++++++---- styles.css | 376 +++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 1004 insertions(+), 184 deletions(-) diff --git a/admin/script.js b/admin/script.js index d6576ba..f2ecb83 100644 --- a/admin/script.js +++ b/admin/script.js @@ -975,28 +975,44 @@ class FileManager { const tbody = document.getElementById('fileList'); const sortBy = document.getElementById('sortBy').value; - let sortedFiles = [...this.files]; + // 검색 및 ν•„ν„° 적용 (메인 νŽ˜μ΄μ§€μ™€ λ™μΌν•˜κ²Œ) + const searchTerm = document.getElementById('searchInput') ? document.getElementById('searchInput').value.toLowerCase().trim() : ''; + const categoryFilter = document.getElementById('categoryFilter') ? document.getElementById('categoryFilter').value : ''; - switch (sortBy) { - case 'title': - sortedFiles.sort((a, b) => a.title.localeCompare(b.title)); - break; - case 'category': - sortedFiles.sort((a, b) => a.category.localeCompare(b.category)); - break; - case 'date': - default: - sortedFiles.sort((a, b) => new Date(b.created_at || b.createdAt) - new Date(a.created_at || a.createdAt)); - break; + let filteredFiles = [...this.files]; + + if (searchTerm) { + filteredFiles = filteredFiles.filter(file => + file.title.toLowerCase().includes(searchTerm) || + file.description.toLowerCase().includes(searchTerm) || + (file.tags && file.tags.some(tag => tag.toLowerCase().includes(searchTerm))) + ); } + + if (categoryFilter) { + filteredFiles = filteredFiles.filter(file => file.category === categoryFilter); + } + + // μ •λ ¬ + const sortedFiles = 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 || b.createdAt) - new Date(a.created_at || a.createdAt); + } + }); - this.allFiles = sortedFiles; // 전체 파일 λͺ©λ‘ μ €μž₯ + this.allFiles = sortedFiles; this.updatePagination(); if (sortedFiles.length === 0) { tbody.innerHTML = ` - πŸ“‚ λ“±λ‘λœ μžλ£Œκ°€ μ—†μŠ΅λ‹ˆλ‹€. μƒˆ 자료λ₯Ό μΆ”κ°€ν•΄λ³΄μ„Έμš”! + πŸ“‚ 쑰건에 λ§žλŠ” μžλ£Œκ°€ μ—†μŠ΅λ‹ˆλ‹€. `; return; @@ -1026,7 +1042,7 @@ class FileManager {
- ${this.escapeHtml(file.title)} + ${this.escapeHtml(file.title)} ${file.description ? `
${this.escapeHtml(file.description.substring(0, 80))}${file.description.length > 80 ? '...' : ''}` : ''} ${file.tags && file.tags.length > 0 ? `
${file.tags.map(tag => `#${this.escapeHtml(tag)}`).join('')}
` : '' @@ -1035,9 +1051,15 @@ class FileManager { ${hasAttachments ? - `
${file.files.map((f, index) => - `${this.getFileIcon(f.name || f.original_name || 'unknown')}` - ).join(' ')}
` : + `
${file.files.map((f, index) => + `
+ ${this.getFileIcon(f.name || f.original_name || 'unknown')} +
+
${this.escapeHtml(f.name || f.original_name || '파일')}
+
${this.formatFileSize(f.size || 0)}
+
+
` + ).join('')}
` : `-` } @@ -1104,23 +1126,23 @@ class FileManager { // νŽ˜μ΄μ§€λ„€μ΄μ…˜ κ΄€λ ¨ ν•¨μˆ˜λ“€ updatePagination() { + const totalPages = Math.max(1, Math.ceil(this.allFiles.length / 10)); const pagination = document.getElementById('pagination'); const prevBtn = document.getElementById('prevPage'); const nextBtn = document.getElementById('nextPage'); const pageInfo = document.getElementById('pageInfo'); - const totalFiles = this.allFiles.length; - const itemsPerPage = 10; - const totalPages = Math.ceil(totalFiles / itemsPerPage); + // 항상 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ ν‘œμ‹œ + if (pagination) pagination.style.display = 'flex'; - if (totalPages <= 1) { - pagination.style.display = 'none'; - } else { - pagination.style.display = 'flex'; - prevBtn.disabled = this.currentPage <= 1; - nextBtn.disabled = this.currentPage >= totalPages; - pageInfo.textContent = `${this.currentPage} / ${totalPages}`; - } + // νŽ˜μ΄μ§€ λ²„νŠΌ μƒνƒœ μ—…λ°μ΄νŠΈ + if (prevBtn) prevBtn.disabled = this.currentPage <= 1; + if (nextBtn) nextBtn.disabled = this.currentPage >= totalPages || this.allFiles.length === 0; + + // νŽ˜μ΄μ§€ 정보 ν‘œμ‹œ (μ•„μ΄ν…œμ΄ 없어도 1/1둜 ν‘œμ‹œ) + const displayTotalPages = this.allFiles.length === 0 ? 1 : totalPages; + const displayCurrentPage = this.allFiles.length === 0 ? 1 : this.currentPage; + if (pageInfo) pageInfo.textContent = `${displayCurrentPage} / ${displayTotalPages}`; } goToPrevPage() { @@ -1145,22 +1167,141 @@ class FileManager { viewFile(id) { const file = this.files.find(f => f.id === id); if (!file) return; + + this.showDetailView(file); + } + + showDetailView(file) { + // 메인 μ»¨ν…Œμ΄λ„ˆ 숨기기 + const container = document.querySelector('.container'); + container.style.display = 'none'; - // κ°„λ‹¨ν•œ μ•Œλ¦ΌμœΌλ‘œ 파일 정보 ν‘œμ‹œ - let info = `πŸ“„ ${file.title}\n\n`; - info += `πŸ“ μΉ΄ν…Œκ³ λ¦¬: ${file.category}\n`; - info += `πŸ“… 등둝일: ${new Date(file.created_at || file.createdAt).toLocaleDateString('ko-KR')}\n`; - if (file.description) info += `πŸ“ μ„€λͺ…: ${file.description}\n`; - if (file.tags && file.tags.length > 0) info += `🏷️ νƒœκ·Έ: ${file.tags.join(', ')}\n`; - if (file.files && file.files.length > 0) { - info += `\nπŸ“Ž μ²¨λΆ€νŒŒμΌ (${file.files.length}개):\n`; - file.files.forEach((attachment, index) => { - const icon = this.getFileIcon(attachment.name || attachment.original_name || 'unknown'); - info += ` ${index + 1}. ${icon} ${attachment.name || attachment.original_name || '파일'}\n`; - }); + // 상세보기 μ»¨ν…Œμ΄λ„ˆ 생성 + const detailContainer = document.createElement('div'); + detailContainer.className = 'detail-container'; + detailContainer.id = 'detailContainer'; + + const createdDate = new Date(file.created_at || file.createdAt).toLocaleDateString('ko-KR'); + const updatedDate = new Date(file.updated_at || file.updatedAt).toLocaleDateString('ko-KR'); + + detailContainer.innerHTML = ` +
+
+

πŸ“‹ 자료 상세보기

+

λ“±λ‘λœ 자료의 상세 정보λ₯Ό ν™•μΈν•˜κ³  κ΄€λ¦¬ν•˜μ„Έμš”

+
+ +
+
+

πŸ“„ ${this.escapeHtml(file.title)}

+
+ + + +
+
+ +
+
+
+ +
+ ${file.category} +
+
+ +
+ +
+ ${file.description ? this.escapeHtml(file.description) : 'μ„€λͺ…이 μ—†μŠ΅λ‹ˆλ‹€.'} +
+
+ + ${file.tags && file.tags.length > 0 ? ` +
+ +
+
+ ${file.tags.map(tag => `#${this.escapeHtml(tag)}`).join('')} +
+
+
` : ''} + + ${file.files && file.files.length > 0 ? ` +
+ +
+
+ ${file.files.map((f, index) => ` +
+ ${this.getFileIcon(f.name || f.original_name || 'unknown')} + ${this.escapeHtml(f.name || f.original_name || '파일')} + +
+ `).join('')} +
+
+ +
+
+
` : ` +
+ +
+ μ²¨λΆ€λœ 파일이 μ—†μŠ΅λ‹ˆλ‹€. +
+
`} + +
+ +
${createdDate}
+
+ + ${createdDate !== updatedDate ? ` +
+ +
${updatedDate}
+
` : ''} +
+
+
+
+ `; + + document.body.appendChild(detailContainer); + } + + hideDetailView() { + const detailContainer = document.getElementById('detailContainer'); + if (detailContainer) { + detailContainer.remove(); } - alert(info); + // 메인 μ»¨ν…Œμ΄λ„ˆ λ‹€μ‹œ 보이기 + const container = document.querySelector('.container'); + container.style.display = 'block'; + } + + editFileFromDetail(id) { + this.hideDetailView(); + this.editFile(id); + } + + async deleteFileFromDetail(id) { + if (confirm('μ •λ§λ‘œ 이 자료λ₯Ό μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?')) { + await this.deleteFile(id); + this.hideDetailView(); + } } editFile(id) { @@ -1331,49 +1472,11 @@ class FileManager { const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); 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.some(tag => tag.toLowerCase().includes(searchTerm)) - ); - } - - if (categoryFilter) { - filteredFiles = filteredFiles.filter(file => file.category === categoryFilter); - } - - this.renderFilteredFiles(filteredFiles); + // 검색 μ‹œ 첫 νŽ˜μ΄μ§€λ‘œ 리셋 + this.currentPage = 1; + this.renderFiles(); } - renderFilteredFiles(files) { - const container = document.getElementById('fileList'); - const sortBy = document.getElementById('sortBy').value; - - let sortedFiles = [...files]; - - switch (sortBy) { - case 'title': - sortedFiles.sort((a, b) => a.title.localeCompare(b.title)); - break; - case 'category': - sortedFiles.sort((a, b) => a.category.localeCompare(b.category)); - break; - case 'date': - default: - sortedFiles.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - break; - } - - if (sortedFiles.length === 0) { - container.innerHTML = '

πŸ” 검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ ν‚€μ›Œλ“œλ‘œ κ²€μƒ‰ν•΄λ³΄μ„Έμš”!

'; - return; - } - - container.innerHTML = sortedFiles.map(file => this.createFileHTML(file)).join(''); - } clearForm() { document.getElementById('fileForm').reset(); diff --git a/admin/styles.css b/admin/styles.css index 1e87c07..724e5a4 100644 --- a/admin/styles.css +++ b/admin/styles.css @@ -385,7 +385,7 @@ header p { } .board-table th { - padding: 15px 12px; + padding: 12px; text-align: center; font-weight: 600; font-size: 0.95rem; @@ -410,17 +410,18 @@ header p { } .board-table td { - padding: 12px; + padding: 8px 12px; text-align: center; vertical-align: middle; font-size: 0.9rem; + line-height: 1.4; } /* 컬럼 λ„ˆλΉ„ μ„€μ • */ .col-no { width: 60px; } .col-category { width: 100px; } .col-title { width: auto; min-width: 200px; text-align: left; } -.col-attachment { width: 80px; } +.col-attachment { width: 220px; text-align: center; } .col-date { width: 120px; } .col-actions { width: 150px; } @@ -431,7 +432,7 @@ header p { text-decoration: none; cursor: pointer; display: block; - padding: 8px; + padding: 4px 6px; border-radius: 4px; transition: all 0.2s ease; } @@ -465,16 +466,61 @@ header p { .attachment-icons { display: flex; - gap: 2px; - justify-content: center; + flex-direction: column; + gap: 4px; align-items: center; - flex-wrap: wrap; - font-size: 1rem; + font-size: 0.85rem; line-height: 1.2; + max-height: 100px; + overflow-y: auto; + padding: 2px; } -.attachment-icons span { - display: inline-block; +.attachment-file-item { + display: inline-flex; + align-items: center; + gap: 2px; + cursor: pointer; + padding: 3px 4px; + border-radius: 3px; + transition: background-color 0.2s ease; + justify-content: center; + white-space: nowrap; +} + +.attachment-file-item:hover { + background-color: #f0f9ff; +} + +.attachment-file-icon { + font-size: 1.1rem; + width: auto; + text-align: left; + flex-shrink: 0; + margin-right: 1px; +} + +.attachment-file-info { + display: flex; + flex-direction: column; + gap: 1px; + flex: 1; + min-width: 0; + margin-left: 0; +} + +.attachment-file-name { + font-weight: 500; + color: #374151; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.8rem; +} + +.attachment-file-size { + color: #6b7280; + font-size: 0.7rem; } .attachment-icon-clickable { @@ -496,15 +542,15 @@ header p { /* μ•‘μ…˜ λ²„νŠΌ */ .action-buttons { display: flex; - gap: 5px; + gap: 3px; justify-content: center; align-items: center; } .action-btn { - padding: 6px 12px; + padding: 4px 8px; border: none; - border-radius: 4px; + border-radius: 3px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s ease; @@ -549,6 +595,298 @@ header p { font-size: 1.1rem; } +/* 상세보기 νŽ˜μ΄μ§€ μŠ€νƒ€μΌ */ +.detail-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + z-index: 1000; + overflow-y: auto; +} + +.detail-section { + background: rgba(255, 255, 255, 0.95); + padding: 30px; + border-radius: 15px; + margin-bottom: 30px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + flex-wrap: wrap; + gap: 15px; +} + +.detail-header h2 { + color: #4a5568; + font-size: 2rem; + margin: 0; + flex: 1; +} + +.detail-actions { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; +} + +.edit-detail-btn { + padding: 10px 20px; + background: #3b82f6; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 0.95rem; + font-weight: 500; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.edit-detail-btn:hover { + background: #2563eb; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); +} + +.delete-detail-btn { + padding: 10px 20px; + background: #ef4444; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 0.95rem; + font-weight: 500; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.delete-detail-btn:hover { + background: #dc2626; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); +} + +.back-btn { + padding: 12px 24px; + background: #667eea; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + transition: all 0.3s ease; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.back-btn:hover { + background: #5a67d8; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.detail-content { + display: grid; + gap: 20px; +} + +.detail-info { + display: grid; + gap: 25px; +} + +.info-group { + display: grid; + gap: 10px; +} + +.info-group label { + font-weight: 600; + color: #4a5568; + font-size: 1.1rem; + display: flex; + align-items: center; + gap: 8px; +} + +.info-value { + padding: 15px; + background: #f8fafc; + border-radius: 8px; + border: 2px solid #e2e8f0; + font-size: 1rem; + line-height: 1.6; +} + +.info-value.description { + min-height: 80px; + white-space: pre-wrap; +} + +.info-value.no-files { + color: #9ca3af; + font-style: italic; +} + +.tags-container { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.tag { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + padding: 6px 12px; + border-radius: 20px; + font-size: 0.9rem; + font-weight: 500; + border: none; +} + +.attachments-list { + display: grid; + gap: 12px; + margin-bottom: 20px; +} + +.attachment-item { + display: flex; + align-items: center; + gap: 15px; + padding: 15px; + background: white; + border-radius: 8px; + border: 1px solid #e5e7eb; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.attachment-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +.attachment-icon { + font-size: 1.5rem; + min-width: 30px; + text-align: center; +} + +.attachment-name { + flex: 1; + font-weight: 500; + color: #374151; + word-break: break-all; +} + +.download-single-btn { + background: #10b981; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + transition: all 0.2s ease; +} + +.download-single-btn:hover { + background: #059669; + transform: scale(1.05); +} + +.attachment-actions { + padding-top: 15px; + border-top: 1px solid #e5e7eb; + text-align: center; +} + +.download-all-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.download-all-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); +} + +/* λͺ¨λ°”일 λ°˜μ‘ν˜• */ +@media (max-width: 768px) { + .detail-section { + padding: 20px; + margin: 10px; + border-radius: 10px; + } + + .detail-header { + flex-direction: column; + align-items: flex-start; + } + + .detail-header h2 { + font-size: 1.5rem; + } + + .detail-actions { + width: 100%; + justify-content: stretch; + } + + .detail-actions button { + flex: 1; + min-width: 0; + } + + .back-btn { + order: 3; + width: 100%; + justify-content: center; + margin-top: 10px; + } + + .attachment-item { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .download-single-btn { + align-self: stretch; + text-align: center; + } +} + /* 파일 μ—…λ‘œλ“œ μ˜μ—­ */ .file-upload-area { position: relative; diff --git a/script.js b/script.js index 0925f61..9ab1282 100644 --- a/script.js +++ b/script.js @@ -65,7 +65,7 @@ class FileManager { const fileList = document.getElementById('fileList'); const sortBy = document.getElementById('sortBy').value; - // μ •λ ¬ + // μ •λ ¬ (κ΄€λ¦¬μž νŽ˜μ΄μ§€μ™€ λ™μΌν•˜κ²Œ) const sortedFiles = [...this.filteredFiles].sort((a, b) => { switch (sortBy) { case 'title': @@ -74,7 +74,7 @@ class FileManager { return a.category.localeCompare(b.category); case 'date': default: - return new Date(b.createdAt) - new Date(a.createdAt); + return new Date(b.created_at || b.createdAt) - new Date(a.created_at || a.createdAt); } }); @@ -118,9 +118,15 @@ class FileManager { ${hasAttachments ? - `
${file.files.map((f, index) => - `${this.getFileIcon(f.name || f.original_name || 'unknown')}` - ).join(' ')}
` : + `
${file.files.map((f, index) => + `
+ ${this.getFileIcon(f.name || f.original_name || 'unknown')} +
+
${this.escapeHtml(f.name || f.original_name || '파일')}
+
${this.formatFileSize(f.size || 0)}
+
+
` + ).join('')}
` : `-` } @@ -151,25 +157,132 @@ class FileManager { 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]; + } + viewFileInfo(id) { const file = this.files.find(f => f.id === id); if (!file) return; - let info = `πŸ“‹ 자료 정보\n\n`; - info += `πŸ“Œ 제λͺ©: ${file.title}\n`; - info += `πŸ“‚ μΉ΄ν…Œκ³ λ¦¬: ${file.category}\n`; - info += `πŸ“… 등둝일: ${new Date(file.created_at || file.createdAt).toLocaleDateString('ko-KR')}\n`; - if (file.description) info += `πŸ“ μ„€λͺ…: ${file.description}\n`; - if (file.tags && file.tags.length > 0) info += `🏷️ νƒœκ·Έ: ${file.tags.join(', ')}\n`; - if (file.files && file.files.length > 0) { - info += `\nπŸ“Ž μ²¨λΆ€νŒŒμΌ (${file.files.length}개):\n`; - file.files.forEach((attachment, index) => { - const icon = this.getFileIcon(attachment.name || attachment.original_name || 'unknown'); - info += ` ${index + 1}. ${icon} ${attachment.name || attachment.original_name || '파일'}\n`; - }); + this.showDetailView(file); + } + + showDetailView(file) { + // 메인 μ»¨ν…Œμ΄λ„ˆ 숨기기 + const container = document.querySelector('.container'); + container.style.display = 'none'; + + // 상세보기 μ»¨ν…Œμ΄λ„ˆ 생성 + const detailContainer = document.createElement('div'); + detailContainer.className = 'detail-container'; + detailContainer.id = 'detailContainer'; + + const createdDate = new Date(file.created_at || file.createdAt).toLocaleDateString('ko-KR'); + const updatedDate = new Date(file.updated_at || file.updatedAt).toLocaleDateString('ko-KR'); + + detailContainer.innerHTML = ` +
+
+

πŸ“‹ 자료 상세보기

+

λ“±λ‘λœ 자료의 상세 정보λ₯Ό ν™•μΈν•˜μ„Έμš”

+
+ +
+
+

πŸ“„ ${this.escapeHtml(file.title)}

+ +
+ +
+
+
+ +
+ ${file.category} +
+
+ +
+ +
+ ${file.description ? this.escapeHtml(file.description) : 'μ„€λͺ…이 μ—†μŠ΅λ‹ˆλ‹€.'} +
+
+ + ${file.tags && file.tags.length > 0 ? ` +
+ +
+
+ ${file.tags.map(tag => `#${this.escapeHtml(tag)}`).join('')} +
+
+
` : ''} + + ${file.files && file.files.length > 0 ? ` +
+ +
+
+ ${file.files.map((f, index) => ` +
+ ${this.getFileIcon(f.name || f.original_name || 'unknown')} + ${this.escapeHtml(f.name || f.original_name || '파일')} + +
+ `).join('')} +
+
+ +
+
+
` : ` +
+ +
+ μ²¨λΆ€λœ 파일이 μ—†μŠ΅λ‹ˆλ‹€. +
+
`} + +
+ +
${createdDate}
+
+ + ${createdDate !== updatedDate ? ` +
+ +
${updatedDate}
+
` : ''} +
+
+
+
+ `; + + document.body.appendChild(detailContainer); + } + + hideDetailView() { + const detailContainer = document.getElementById('detailContainer'); + if (detailContainer) { + detailContainer.remove(); } - alert(info); + // 메인 μ»¨ν…Œμ΄λ„ˆ λ‹€μ‹œ 보이기 + const container = document.querySelector('.container'); + container.style.display = 'block'; } async downloadFiles(id) { @@ -262,20 +375,23 @@ class FileManager { } updatePagination() { - const totalPages = Math.ceil(this.filteredFiles.length / this.itemsPerPage); + 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 (totalPages <= 1) { - pagination.style.display = 'none'; - } else { - pagination.style.display = 'flex'; - prevBtn.disabled = this.currentPage <= 1; - nextBtn.disabled = this.currentPage >= totalPages; - pageInfo.textContent = `${this.currentPage} / ${totalPages}`; - } + // 항상 νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ ν‘œμ‹œ + pagination.style.display = 'flex'; + + // νŽ˜μ΄μ§€ λ²„νŠΌ μƒνƒœ μ—…λ°μ΄νŠΈ + prevBtn.disabled = this.currentPage <= 1; + nextBtn.disabled = this.currentPage >= totalPages || this.filteredFiles.length === 0; + + // νŽ˜μ΄μ§€ 정보 ν‘œμ‹œ (μ•„μ΄ν…œμ΄ 없어도 1/1둜 ν‘œμ‹œ) + const displayTotalPages = this.filteredFiles.length === 0 ? 1 : totalPages; + const displayCurrentPage = this.filteredFiles.length === 0 ? 1 : this.currentPage; + pageInfo.textContent = `${displayCurrentPage} / ${displayTotalPages}`; } goToPrevPage() { @@ -299,7 +415,14 @@ class FileManager { loadFiles() { try { const stored = localStorage.getItem('fileManagerData'); - return stored ? JSON.parse(stored) : []; + const files = stored ? JSON.parse(stored) : []; + + // κΈ°μ‘΄ localStorage λ°μ΄ν„°μ˜ ν˜Έν™˜μ„±μ„ μœ„ν•΄ 컴럼λͺ… λ³€ν™˜ + return files.map(file => ({ + ...file, + created_at: file.created_at || file.createdAt || new Date().toISOString(), + updated_at: file.updated_at || file.updatedAt || new Date().toISOString() + })); } catch (error) { console.error('파일 λ‘œλ“œ 쀑 였λ₯˜:', error); return []; diff --git a/styles.css b/styles.css index dd9690b..c10cf1e 100644 --- a/styles.css +++ b/styles.css @@ -40,45 +40,6 @@ header p { } /* νŽ˜μ΄μ§€λ„€μ΄μ…˜ μŠ€νƒ€μΌ */ -.pagination { - display: flex; - justify-content: center; - align-items: center; - gap: 20px; - margin-top: 30px; - padding: 20px; - background: rgba(255, 255, 255, 0.9); - border-radius: 10px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); -} - -.page-btn { - background: #4299e1; - color: white; - border: none; - padding: 10px 20px; - border-radius: 8px; - cursor: pointer; - font-size: 1rem; - transition: all 0.3s ease; -} - -.page-btn:hover:not(:disabled) { - background: #3182ce; - transform: translateY(-2px); -} - -.page-btn:disabled { - background: #a0aec0; - cursor: not-allowed; - transform: none; -} - -#pageInfo { - font-weight: 600; - color: #4a5568; - font-size: 1.1rem; -} .auth-section { margin-top: 20px; @@ -426,7 +387,7 @@ header p { } .board-table th { - padding: 15px 12px; + padding: 12px; text-align: center; font-weight: 600; font-size: 0.95rem; @@ -451,17 +412,18 @@ header p { } .board-table td { - padding: 12px; + padding: 8px 12px; text-align: center; vertical-align: middle; font-size: 0.9rem; + line-height: 1.4; } /* 컬럼 λ„ˆλΉ„ μ„€μ • */ .col-no { width: 60px; } .col-category { width: 100px; } .col-title { width: auto; min-width: 200px; text-align: left; } -.col-attachment { width: 80px; } +.col-attachment { width: 220px; text-align: center; } .col-date { width: 120px; } .col-actions { width: 150px; } @@ -472,7 +434,7 @@ header p { text-decoration: none; cursor: pointer; display: block; - padding: 8px; + padding: 4px 6px; border-radius: 4px; transition: all 0.2s ease; } @@ -506,16 +468,61 @@ header p { .attachment-icons { display: flex; - gap: 2px; - justify-content: center; + flex-direction: column; + gap: 4px; align-items: center; - flex-wrap: wrap; - font-size: 1rem; + font-size: 0.85rem; line-height: 1.2; + max-height: 100px; + overflow-y: auto; + padding: 2px; } -.attachment-icons span { - display: inline-block; +.attachment-file-item { + display: inline-flex; + align-items: center; + gap: 2px; + cursor: pointer; + padding: 3px 4px; + border-radius: 3px; + transition: background-color 0.2s ease; + justify-content: center; + white-space: nowrap; +} + +.attachment-file-item:hover { + background-color: #f0f9ff; +} + +.attachment-file-icon { + font-size: 1.1rem; + width: auto; + text-align: left; + flex-shrink: 0; + margin-right: 1px; +} + +.attachment-file-info { + display: flex; + flex-direction: column; + gap: 1px; + flex: 1; + min-width: 0; + margin-left: 0; +} + +.attachment-file-name { + font-weight: 500; + color: #374151; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.8rem; +} + +.attachment-file-size { + color: #6b7280; + font-size: 0.7rem; } .attachment-icon-clickable { @@ -537,15 +544,15 @@ header p { /* μ•‘μ…˜ λ²„νŠΌ */ .action-buttons { display: flex; - gap: 5px; + gap: 3px; justify-content: center; align-items: center; } .action-btn { - padding: 6px 12px; + padding: 4px 8px; border: none; - border-radius: 4px; + border-radius: 3px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s ease; @@ -575,39 +582,288 @@ header p { display: flex; justify-content: center; align-items: center; - gap: 15px; - margin-top: 20px; - padding: 20px 0; + gap: 20px; + margin-top: 30px; + padding: 20px; + background: rgba(255, 255, 255, 0.9); + border-radius: 10px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); } .page-btn { - padding: 8px 16px; + padding: 12px 20px; border: 2px solid #e2e8f0; - border-radius: 6px; + border-radius: 8px; background: white; color: #4a5568; cursor: pointer; - font-size: 0.9rem; - font-weight: 500; - transition: all 0.2s ease; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; + min-width: 80px; } .page-btn:hover:not(:disabled) { border-color: #667eea; color: #667eea; background: #f0f4ff; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } .page-btn:disabled { opacity: 0.5; cursor: not-allowed; color: #9ca3af; + transform: none; + box-shadow: none; } #pageInfo { + font-weight: 700; + color: #4a5568; + font-size: 1.2rem; + background: linear-gradient(135deg, #667eea, #764ba2); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + padding: 8px 16px; + border: 2px solid #667eea; + border-radius: 8px; + background-color: rgba(102, 126, 234, 0.1); + min-width: 80px; + text-align: center; +} + +/* 상세보기 νŽ˜μ΄μ§€ μŠ€νƒ€μΌ */ +.detail-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + z-index: 1000; + overflow-y: auto; +} + +.detail-section { + background: rgba(255, 255, 255, 0.95); + padding: 30px; + border-radius: 15px; + margin-bottom: 30px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + flex-wrap: wrap; + gap: 15px; +} + +.detail-header h2 { + color: #4a5568; + font-size: 2rem; + margin: 0; + flex: 1; +} + +.back-btn { + padding: 12px 24px; + background: #667eea; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + transition: all 0.3s ease; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.back-btn:hover { + background: #5a67d8; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.detail-content { + display: grid; + gap: 20px; +} + +.detail-info { + display: grid; + gap: 25px; +} + +.info-group { + display: grid; + gap: 10px; +} + +.info-group label { font-weight: 600; color: #4a5568; + font-size: 1.1rem; + display: flex; + align-items: center; + gap: 8px; +} + +.info-value { + padding: 15px; + background: #f8fafc; + border-radius: 8px; + border: 2px solid #e2e8f0; font-size: 1rem; + line-height: 1.6; +} + +.info-value.description { + min-height: 80px; + white-space: pre-wrap; +} + +.info-value.no-files { + color: #9ca3af; + font-style: italic; +} + +.tags-container { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.tag { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + padding: 6px 12px; + border-radius: 20px; + font-size: 0.9rem; + font-weight: 500; + border: none; +} + +.attachments-list { + display: grid; + gap: 12px; + margin-bottom: 20px; +} + +.attachment-item { + display: flex; + align-items: center; + gap: 15px; + padding: 15px; + background: white; + border-radius: 8px; + border: 1px solid #e5e7eb; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.attachment-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +.attachment-icon { + font-size: 1.5rem; + min-width: 30px; + text-align: center; +} + +.attachment-name { + flex: 1; + font-weight: 500; + color: #374151; + word-break: break-all; +} + +.download-single-btn { + background: #10b981; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + transition: all 0.2s ease; +} + +.download-single-btn:hover { + background: #059669; + transform: scale(1.05); +} + +.attachment-actions { + padding-top: 15px; + border-top: 1px solid #e5e7eb; + text-align: center; +} + +.download-all-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.download-all-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); +} + +/* λͺ¨λ°”일 λ°˜μ‘ν˜• */ +@media (max-width: 768px) { + .detail-section { + padding: 20px; + margin: 10px; + border-radius: 10px; + } + + .detail-header { + flex-direction: column; + align-items: flex-start; + } + + .detail-header h2 { + font-size: 1.5rem; + } + + .back-btn { + width: 100%; + justify-content: center; + } + + .attachment-item { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .download-single-btn { + align-self: stretch; + text-align: center; + } } .file-list {