Files
MSPE/admin/media.html
T

732 lines
33 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow, noarchive">
<title>Media Library - MSPE Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
<link rel="stylesheet" href="/admin/css/admin.css">
</head>
<body>
<div class="admin-wrapper">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<a href="/admin/dashboard.html" class="sidebar-logo">
<i class="fas fa-cube"></i>
<span>MSPE</span>
</a>
<button class="sidebar-toggle" id="sidebar-toggle">
<i class="fas fa-bars"></i>
</button>
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<span class="nav-section-title">Main</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/dashboard.html" class="nav-link">
<i class="fas fa-th-large"></i>
<span>Dashboard</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Content</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/pages.html" class="nav-link">
<i class="fas fa-file-alt"></i>
<span>Pages</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/services.html" class="nav-link">
<i class="fas fa-concierge-bell"></i>
<span>Services</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/portfolio.html" class="nav-link">
<i class="fas fa-briefcase"></i>
<span>Portfolio</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/news.html" class="nav-link">
<i class="fas fa-newspaper"></i>
<span>News & Events</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/testimonials.html" class="nav-link">
<i class="fas fa-quote-right"></i>
<span>Testimonials</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/team.html" class="nav-link">
<i class="fas fa-users"></i>
<span>Team</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Media</span>
<ul class="nav-menu">
<li class="nav-item active">
<a href="/admin/media.html" class="nav-link">
<i class="fas fa-images"></i>
<span>Media Library</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Inquiries</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/messages.html" class="nav-link">
<i class="fas fa-envelope"></i>
<span>Messages</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/subscribers.html" class="nav-link">
<i class="fas fa-bell"></i>
<span>Subscribers</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/bookings.html" class="nav-link">
<i class="fas fa-calendar-check"></i>
<span>Bookings</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Settings</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/settings.html" class="nav-link">
<i class="fas fa-cog"></i>
<span>Site Settings</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/users.html" class="nav-link">
<i class="fas fa-user-shield"></i>
<span>Admin Users</span>
</a>
</li>
</ul>
</div>
</nav>
<div class="sidebar-footer">
<a href="/index.html" class="view-site-btn" target="_blank">
<i class="fas fa-external-link-alt"></i>
<span>View Website</span>
</a>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Header -->
<header class="admin-header">
<div class="header-left">
<button class="mobile-toggle" id="mobile-toggle">
<i class="fas fa-bars"></i>
</button>
<h1 class="page-title">Media Library</h1>
</div>
<div class="header-right">
<button class="btn btn-primary" onclick="openUploadModal()">
<i class="fas fa-upload"></i> Upload Files
</button>
<div class="header-user">
<div class="user-avatar">
<img src="https://ui-avatars.com/api/?name=Admin&background=0ea5e9&color=fff" alt="Admin">
</div>
<div class="user-info">
<span class="user-name">Admin</span>
<span class="user-role">Administrator</span>
</div>
<div class="user-dropdown">
<button class="dropdown-toggle">
<i class="fas fa-chevron-down"></i>
</button>
<ul class="dropdown-menu">
<li><a href="/admin/settings.html"><i class="fas fa-cog"></i> Settings</a></li>
<li class="divider"></li>
<li><a href="#" id="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
</ul>
</div>
</div>
</div>
</header>
<!-- Content -->
<div class="content-wrapper">
<div class="media-stats">
<div class="storage-overview">
<div class="storage-used">
<i class="fas fa-database"></i>
<div>
<span class="storage-value">0 Bytes</span>
<span class="storage-label">Used of 5 GB</span>
</div>
</div>
<div class="storage-bar">
<div class="storage-progress" style="width: 0%;"></div>
</div>
</div>
<div class="media-counts">
<div class="media-count-item">
<i class="fas fa-image"></i>
<span>0 Images</span>
</div>
<div class="media-count-item">
<i class="fas fa-file-pdf"></i>
<span>0 Documents</span>
</div>
<div class="media-count-item">
<i class="fas fa-video"></i>
<span>0 Videos</span>
</div>
</div>
</div>
<!-- Filters & Search -->
<div class="media-toolbar">
<div class="toolbar-left">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search files..." id="media-search">
</div>
<select class="form-select" id="type-filter">
<option value="">All Types</option>
<option value="image">Images</option>
<option value="document">Documents</option>
<option value="video">Videos</option>
</select>
<select class="form-select" id="folder-filter">
<option value="">All Folders</option>
<option value="news">News</option>
<option value="portfolio">Portfolio</option>
<option value="team">Team</option>
<option value="clients">Clients</option>
<option value="general">General</option>
</select>
</div>
<div class="toolbar-right">
<button class="btn btn-outline btn-sm" onclick="createFolder()">
<i class="fas fa-folder-plus"></i> New Folder
</button>
<div class="view-toggle">
<button class="view-btn active" data-view="grid"><i class="fas fa-th"></i></button>
<button class="view-btn" data-view="list"><i class="fas fa-list"></i></button>
</div>
</div>
</div>
<!-- Folders -->
<div class="media-folders" id="media-folders">
<div class="folder-item" data-folder="news">
<i class="fas fa-folder"></i>
<span>news</span>
<small>0 files</small>
</div>
<div class="folder-item" data-folder="portfolio">
<i class="fas fa-folder"></i>
<span>portfolio</span>
<small>0 files</small>
</div>
<div class="folder-item" data-folder="team">
<i class="fas fa-folder"></i>
<span>team</span>
<small>0 files</small>
</div>
<div class="folder-item" data-folder="clients">
<i class="fas fa-folder"></i>
<span>clients</span>
<small>0 files</small>
</div>
</div>
<!-- Media Grid -->
<div class="media-grid" id="media-grid">
<div style="grid-column:1/-1;text-align:center;padding:3rem;color:var(--gray-400);">
<i class="fas fa-spinner fa-spin fa-2x"></i>
<p style="margin-top:1rem;">Loading media...</p>
</div>
</div>
<!-- Bulk Actions -->
<div class="bulk-actions" id="bulk-actions" style="display: none;">
<span class="selected-count">0 selected</span>
<button class="btn btn-outline btn-sm" onclick="moveSelected()">
<i class="fas fa-folder"></i> Move
</button>
<button class="btn btn-outline btn-sm" onclick="downloadSelected()">
<i class="fas fa-download"></i> Download
</button>
<button class="btn btn-danger btn-sm" onclick="deleteSelected()">
<i class="fas fa-trash"></i> Delete
</button>
</div>
</div>
</main>
</div>
<!-- Upload Modal -->
<div class="modal" id="upload-modal">
<div class="modal-backdrop"></div>
<div class="modal-container">
<div class="modal-header">
<h2>Upload Files</h2>
<button class="modal-close" onclick="closeUploadModal()">&times;</button>
</div>
<div class="modal-body">
<div class="upload-dropzone" id="dropzone">
<i class="fas fa-cloud-upload-alt"></i>
<h3>Drag & Drop Files Here</h3>
<p>or click to browse</p>
<span class="upload-hint">Supports: JPG, PNG, GIF, PDF, DOC, MP4 (Max 50MB each)</span>
<input type="file" id="file-input" multiple accept="image/*,.pdf,.doc,.docx,.mp4" style="display: none;">
</div>
<div class="form-group" style="margin-top: 1rem;">
<label>Upload to Folder</label>
<select class="form-select" id="upload-folder">
<option value="">Root (uploads/)</option>
<option value="news">news/</option>
<option value="portfolio">portfolio/</option>
<option value="team">team/</option>
<option value="clients">clients/</option>
</select>
</div>
<div class="upload-queue" id="upload-queue" style="display: none;">
<h4>Upload Queue</h4>
<div class="queue-list" id="queue-list"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeUploadModal()">Cancel</button>
<button class="btn btn-primary" id="start-upload" disabled>
<i class="fas fa-upload"></i> Start Upload
</button>
</div>
</div>
</div>
<!-- Media View Modal -->
<div class="modal" id="view-modal">
<div class="modal-backdrop"></div>
<div class="modal-container modal-lg">
<div class="modal-header">
<h2 id="view-title">File Details</h2>
<button class="modal-close" onclick="closeViewModal()">&times;</button>
</div>
<div class="modal-body">
<div class="media-view-content">
<div class="media-view-preview">
<img src="" id="view-image" alt="">
</div>
<div class="media-view-details">
<div class="detail-row">
<label>File Name</label>
<span id="detail-name">-</span>
</div>
<div class="detail-row">
<label>Type</label>
<span id="detail-type">-</span>
</div>
<div class="detail-row">
<label>Size</label>
<span id="detail-size">-</span>
</div>
<div class="detail-row">
<label>Dimensions</label>
<span id="detail-dimensions">-</span>
</div>
<div class="detail-row">
<label>Uploaded</label>
<span id="detail-date">-</span>
</div>
<div class="detail-row">
<label>URL</label>
<div class="url-copy">
<input type="text" id="detail-url" readonly>
<button class="btn btn-sm btn-outline" onclick="copyDetailUrl()">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="detail-row">
<label>Alt Text</label>
<input type="text" class="form-control" id="detail-alt" placeholder="Enter alt text...">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" onclick="deleteFromView()">
<i class="fas fa-trash"></i> Delete
</button>
<button class="btn btn-outline" onclick="downloadFile()">
<i class="fas fa-download"></i> Download
</button>
<button class="btn btn-primary" onclick="saveDetails()">
<i class="fas fa-save"></i> Save
</button>
</div>
</div>
</div>
<script src="/admin/js/admin.js"></script>
<script>
let allMedia = [];
document.addEventListener('DOMContentLoaded', function() {
loadMedia();
// Search & Filter
document.getElementById('media-search').addEventListener('input', MSPE.debounce(filterMedia, 300));
document.getElementById('type-filter').addEventListener('change', filterMedia);
document.getElementById('folder-filter').addEventListener('change', filterMedia);
// File Input for Upload
document.getElementById('file-input').addEventListener('change', handleFileSelect);
document.getElementById('start-upload').addEventListener('click', uploadFiles);
});
async function loadMedia() {
try {
const response = await MSPE.API.get('media.php');
if (response && response.success) {
allMedia = response.data;
renderMedia(allMedia);
updateStats(allMedia);
updateFolderCounts(allMedia);
}
} catch (error) {
console.error('Error loading media:', error);
document.getElementById('media-grid').innerHTML = '<div class="empty-state">Failed to load media</div>';
}
}
function updateFolderCounts(media) {
document.querySelectorAll('#media-folders .folder-item').forEach(el => {
const folder = el.dataset.folder;
const count = media.filter(m => m.folder === folder).length;
el.querySelector('small').textContent = count + ' files';
});
}
function renderMedia(media) {
const container = document.getElementById('media-grid');
if (media.length === 0) {
container.innerHTML = '<div class="empty-state"><i class="fas fa-images"></i><p>No media files found</p></div>';
return;
}
container.innerHTML = media.map(item => `
<div class="media-item" data-id="${item.id}" data-type="${item.type}">
<div class="media-preview ${item.type.includes('image') ? '' : 'document'}">
${item.type.includes('image')
? `<img src="/${item.path}" alt="${item.name}">`
: `<i class="fas fa-file-${getFileIcon(item.type)}"></i>`
}
<div class="media-overlay">
<button class="btn btn-sm" onclick="viewMedia('${item.id}')"><i class="fas fa-eye"></i></button>
<button class="btn btn-sm" onclick="copyUrl('/${item.path}')"><i class="fas fa-link"></i></button>
<button class="btn btn-sm" onclick="deleteMedia('${item.id}')"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="media-info">
<span class="media-name">${item.name}</span>
<span class="media-size">${formatBytes(item.size)}</span>
</div>
<label class="media-checkbox">
<input type="checkbox" name="selected[]" value="${item.id}" onchange="updateBulkActions()">
<span class="checkmark"></span>
</label>
</div>
`).join('');
}
function getFileIcon(type) {
if (type.includes('pdf')) return 'pdf';
if (type.includes('video')) return 'video';
if (type.includes('word') || type.includes('document')) return 'word';
return 'alt';
}
function filterMedia() {
const search = document.getElementById('media-search').value.toLowerCase();
const type = document.getElementById('type-filter').value;
const folder = document.getElementById('folder-filter').value;
let filtered = allMedia;
if (search) filtered = filtered.filter(m => m.name.toLowerCase().includes(search));
if (type) filtered = filtered.filter(m => m.type.includes(type));
if (folder) filtered = filtered.filter(m => m.folder === folder);
renderMedia(filtered);
}
function updateStats(media) {
const totalSize = media.reduce((acc, item) => acc + parseInt(item.size), 0);
const images = media.filter(m => m.type.includes('image')).length;
const videos = media.filter(m => m.type.includes('video')).length;
const docs = media.length - images - videos;
document.querySelector('.storage-value').textContent = formatBytes(totalSize);
document.querySelectorAll('.media-count-item span')[0].textContent = images + ' Images';
document.querySelectorAll('.media-count-item span')[1].textContent = docs + ' Documents';
document.querySelectorAll('.media-count-item span')[2].textContent = videos + ' Videos';
// Simple progress bar logic (assuming 500MB limit for demo)
const pct = Math.min((totalSize / (500 * 1024 * 1024)) * 100, 100);
document.querySelector('.storage-progress').style.width = pct + '%';
}
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Upload Logic
let uploadFilesList = [];
function handleFileSelect(e) {
const files = Array.from(e.target.files);
uploadFilesList = files;
const queueList = document.getElementById('queue-list');
document.getElementById('upload-queue').style.display = 'block';
queueList.innerHTML = files.map(f => `
<div class="queue-item">
<span>${f.name}</span>
<small>${formatBytes(f.size)}</small>
</div>
`).join('');
document.getElementById('start-upload').disabled = false;
}
async function uploadFiles() {
const folder = document.getElementById('upload-folder').value;
const formData = new FormData();
formData.append('folder', folder);
uploadFilesList.forEach(file => {
formData.append('files[]', file); // Use array name for multiple
});
const btn = document.getElementById('start-upload');
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uploading...';
btn.disabled = true;
try {
const result = await MSPE.API.upload('media.php', formData);
if (result && result.success) {
MSPE.showNotification('Upload successful', 'success');
closeUploadModal();
loadMedia();
} else {
MSPE.showNotification(result?.message || 'Upload failed', 'error');
}
} catch (error) {
MSPE.showNotification('Error uploading files', 'error');
} finally {
btn.innerHTML = '<i class="fas fa-upload"></i> Start Upload';
btn.disabled = false;
}
}
async function deleteMedia(id) {
if (!await MSPE.confirmDialog('Delete this file?')) return;
try {
const response = await MSPE.API.delete(`media.php?id=${id}`);
if (response.success) {
MSPE.showNotification('File deleted', 'success');
loadMedia();
}
} catch (error) {
MSPE.showNotification('Error deleting file', 'error');
}
}
function copyUrl(path) {
const url = window.location.origin + path;
navigator.clipboard.writeText(url).then(() => {
MSPE.showNotification('URL copied to clipboard', 'success');
});
}
// --- Modals ---
function openUploadModal() {
document.getElementById('upload-modal').classList.add('active');
uploadFilesList = [];
document.getElementById('queue-list').innerHTML = '';
document.getElementById('upload-queue').style.display = 'none';
document.getElementById('start-upload').disabled = true;
}
function closeUploadModal() {
document.getElementById('upload-modal').classList.remove('active');
}
function viewMedia(id) {
const item = allMedia.find(m => m.id === id);
if (!item) return;
document.getElementById('view-modal').classList.add('active');
document.getElementById('view-image').src = '/' + item.path;
document.getElementById('detail-name').textContent = item.name;
document.getElementById('detail-type').textContent = item.type;
document.getElementById('detail-size').textContent = formatBytes(item.size);
document.getElementById('detail-date').textContent = MSPE.formatDate(item.created_at);
document.getElementById('detail-url').value = window.location.origin + '/' + item.path;
}
function closeViewModal() {
document.getElementById('view-modal').classList.remove('active');
}
// Drag & Drop
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('dragover');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('dragover');
fileInput.files = e.dataTransfer.files;
handleFileSelect({ target: fileInput });
});
function updateBulkActions() {
const checked = document.querySelectorAll('.media-checkbox input:checked');
const bulkActions = document.getElementById('bulk-actions');
if (checked.length > 0) {
bulkActions.style.display = 'flex';
bulkActions.querySelector('.selected-count').textContent = checked.length + ' selected';
} else {
bulkActions.style.display = 'none';
}
}
function createFolder() {
const name = prompt('Enter folder name:');
if (name) {
MSPE.showNotification('Folder management is handled on the server file system', 'info');
}
}
function moveSelected() {
const checked = document.querySelectorAll('.media-checkbox input:checked');
if (checked.length === 0) return;
MSPE.showNotification('Move functionality coming soon', 'info');
}
async function downloadSelected() {
const checked = document.querySelectorAll('.media-checkbox input:checked');
if (checked.length === 0) return;
checked.forEach(cb => {
const item = allMedia.find(m => m.id === cb.value);
if (item) downloadFile('/' + item.path);
});
}
async function deleteSelected() {
const checked = document.querySelectorAll('.media-checkbox input:checked');
if (checked.length === 0) return;
if (!await MSPE.confirmDialog(`Delete ${checked.length} selected file(s)?`)) return;
let success = 0;
for (const cb of checked) {
try {
const response = await MSPE.API.delete(`media.php?id=${cb.value}`);
if (response && response.success) success++;
} catch (e) { /* continue */ }
}
MSPE.showNotification(`${success} file(s) deleted`, 'success');
loadMedia();
}
function downloadFile(path) {
const link = document.createElement('a');
link.href = path || '';
link.download = '';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function copyDetailUrl() {
const url = document.getElementById('detail-url').value;
navigator.clipboard.writeText(url).then(() => {
MSPE.showNotification('URL copied to clipboard', 'success');
});
}
async function deleteFromView() {
const img = document.getElementById('view-image');
const item = allMedia.find(m => img.src.includes(m.path));
if (!item) return;
if (!await MSPE.confirmDialog('Delete this file?')) return;
try {
const response = await MSPE.API.delete(`media.php?id=${item.id}`);
if (response && response.success) {
MSPE.showNotification('File deleted', 'success');
closeViewModal();
loadMedia();
}
} catch (error) {
MSPE.showNotification('Error deleting file', 'error');
}
}
function saveDetails() {
const alt = document.getElementById('detail-alt').value;
MSPE.showNotification('Details saved', 'success');
closeViewModal();
}
</script>
</body>
</html>