547 lines
25 KiB
HTML
Executable File
547 lines
25 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>Testimonials - 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 active">
|
|
<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">
|
|
<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">Testimonials</h1>
|
|
</div>
|
|
|
|
<div class="header-right">
|
|
<button class="btn btn-primary" onclick="openAddTestimonialModal()">
|
|
<i class="fas fa-plus"></i> Add Testimonial
|
|
</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="content-header">
|
|
<p class="content-description">Manage client testimonials and reviews displayed on the website</p>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats-row" style="margin-bottom: 2rem;">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: rgba(14, 165, 233, 0.15); color: #0ea5e9;">
|
|
<i class="fas fa-quote-right"></i>
|
|
</div>
|
|
<div class="stat-details">
|
|
<span class="stat-value" id="stat-total">0</span>
|
|
<span class="stat-label">Total Testimonials</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: rgba(16, 185, 129, 0.15); color: #10b981;">
|
|
<i class="fas fa-eye"></i>
|
|
</div>
|
|
<div class="stat-details">
|
|
<span class="stat-value" id="stat-published">0</span>
|
|
<span class="stat-label">Published</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: rgba(245, 158, 11, 0.15); color: #f59e0b;">
|
|
<i class="fas fa-star"></i>
|
|
</div>
|
|
<div class="stat-details">
|
|
<span class="stat-value" id="stat-avg-rating">0</span>
|
|
<span class="stat-label">Avg Rating</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: rgba(139, 92, 246, 0.15); color: #8b5cf6;">
|
|
<i class="fas fa-star-half-alt"></i>
|
|
</div>
|
|
<div class="stat-details">
|
|
<span class="stat-value" id="stat-pending">0</span>
|
|
<span class="stat-label">Pending Review</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Testimonials List -->
|
|
<div class="testimonials-admin-grid" id="testimonials-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 testimonials...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Add/Edit Testimonial Modal -->
|
|
<div class="modal" id="testimonial-modal">
|
|
<div class="modal-backdrop"></div>
|
|
<div class="modal-container">
|
|
<div class="modal-header">
|
|
<h2 id="modal-title">Add Testimonial</h2>
|
|
<button class="modal-close" onclick="closeModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="testimonial-form">
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Client Name *</label>
|
|
<input type="text" class="form-control" name="name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Job Title / Position</label>
|
|
<input type="text" class="form-control" name="position">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Company</label>
|
|
<input type="text" class="form-control" name="company">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Rating *</label>
|
|
<select class="form-select" name="rating" required>
|
|
<option value="5">5 Stars - Excellent</option>
|
|
<option value="4.5">4.5 Stars</option>
|
|
<option value="4">4 Stars - Very Good</option>
|
|
<option value="3.5">3.5 Stars</option>
|
|
<option value="3">3 Stars - Good</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Testimonial Text *</label>
|
|
<textarea class="form-control" name="text" rows="4" required placeholder="Enter the client's testimonial..."></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Client Photo</label>
|
|
<div class="file-upload-area">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Drag & drop or click to upload</p>
|
|
<span>Recommended: 200x200px, JPG or PNG</span>
|
|
<input type="file" name="photo" accept="image/*">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Display On</label>
|
|
<div class="checkbox-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="pages[]" value="home" checked> Home Page
|
|
</label>
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="pages[]" value="about"> About Page
|
|
</label>
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="pages[]" value="services"> Services Page
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Status</label>
|
|
<select class="form-select" name="status">
|
|
<option value="published">Published</option>
|
|
<option value="pending">Pending Review</option>
|
|
<option value="hidden">Hidden</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-outline" onclick="closeModal()">Cancel</button>
|
|
<button class="btn btn-primary" onclick="saveTestimonial()">
|
|
<i class="fas fa-save"></i> Save Testimonial
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/admin/js/admin.js"></script>
|
|
<script>
|
|
let allTestimonials = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadTestimonials();
|
|
});
|
|
|
|
async function loadTestimonials() {
|
|
try {
|
|
const response = await MSPE.API.get('testimonials.php');
|
|
if (response && response.success) {
|
|
allTestimonials = response.data;
|
|
renderTestimonials(allTestimonials);
|
|
updateStats(allTestimonials);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading testimonials:', error);
|
|
document.querySelector('#testimonials-grid').innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<p>Failed to load testimonials</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function renderTestimonials(items) {
|
|
const container = document.querySelector('#testimonials-grid');
|
|
if (items.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="fas fa-quote-right"></i>
|
|
<p>No testimonials yet</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items.map(item => `
|
|
<div class="testimonial-admin-card ${item.status}">
|
|
<div class="testimonial-header">
|
|
<div class="testimonial-author">
|
|
<img src="${item.photo || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(item.name) + '&background=random'}" alt="${item.name}">
|
|
<div>
|
|
<h4>${item.name}</h4>
|
|
<span>${item.position || ''} ${item.company ? '@ ' + item.company : ''}</span>
|
|
</div>
|
|
</div>
|
|
<div class="testimonial-status ${item.status}">
|
|
<i class="fas fa-${item.status === 'published' ? 'check-circle' : 'clock'}"></i> ${item.status}
|
|
</div>
|
|
</div>
|
|
<div class="testimonial-rating">
|
|
${renderStars(item.rating)}
|
|
</div>
|
|
<p class="testimonial-text">"${item.text}"</p>
|
|
<div class="testimonial-meta">
|
|
<span><i class="fas fa-calendar"></i> ${MSPE.formatDate(item.created_at)}</span>
|
|
</div>
|
|
<div class="testimonial-admin-actions">
|
|
${item.status === 'pending' ? `
|
|
<button class="btn btn-sm btn-primary" onclick="updateStatus('${item.id}', 'published')">
|
|
<i class="fas fa-check"></i> Approve
|
|
</button>
|
|
` : `
|
|
<button class="btn btn-sm btn-outline" onclick="updateStatus('${item.id}', 'pending')">
|
|
<i class="fas fa-eye-slash"></i> Unpublish
|
|
</button>
|
|
`}
|
|
<button class="btn btn-sm btn-outline" onclick="editTestimonial('${item.id}')">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteTestimonial('${item.id}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderStars(rating) {
|
|
let stars = '';
|
|
for (let i = 1; i <= 5; i++) {
|
|
if (i <= rating) {
|
|
stars += '<i class="fas fa-star"></i>';
|
|
} else if (i - 0.5 <= rating) {
|
|
stars += '<i class="fas fa-star-half-alt"></i>';
|
|
} else {
|
|
stars += '<i class="far fa-star"></i>';
|
|
}
|
|
}
|
|
return stars;
|
|
}
|
|
|
|
function updateStats(items) {
|
|
const total = items.length;
|
|
const published = items.filter(i => i.status === 'published').length;
|
|
const pending = items.filter(i => i.status === 'pending').length;
|
|
const avgRating = total > 0 ? (items.reduce((acc, i) => acc + parseFloat(i.rating), 0) / total).toFixed(1) : 0;
|
|
|
|
document.getElementById('stat-total').textContent = total;
|
|
document.getElementById('stat-published').textContent = published;
|
|
document.getElementById('stat-avg-rating').textContent = avgRating;
|
|
document.getElementById('stat-pending').textContent = pending;
|
|
}
|
|
|
|
function resetPhotoArea(form) {
|
|
const area = form.querySelector('.file-upload-area');
|
|
if (!area) return;
|
|
area.style.position = '';
|
|
area.innerHTML = `
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Drag & drop or click to upload</p>
|
|
<span>Recommended: 200x200px, JPG or PNG</span>
|
|
<input type="file" name="photo" accept="image/*">`;
|
|
}
|
|
|
|
function openAddTestimonialModal() {
|
|
document.getElementById('modal-title').textContent = 'Add Testimonial';
|
|
const form = document.getElementById('testimonial-form');
|
|
form.reset();
|
|
form.dataset.id = '';
|
|
resetPhotoArea(form);
|
|
document.getElementById('testimonial-modal').classList.add('active');
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('testimonial-modal').classList.remove('active');
|
|
}
|
|
|
|
function editTestimonial(id) {
|
|
const item = allTestimonials.find(i => i.id === id);
|
|
if (!item) return;
|
|
|
|
document.getElementById('modal-title').textContent = 'Edit Testimonial';
|
|
const form = document.getElementById('testimonial-form');
|
|
form.dataset.id = id;
|
|
|
|
form.querySelector('[name="name"]').value = item.name;
|
|
form.querySelector('[name="position"]').value = item.position || '';
|
|
form.querySelector('[name="company"]').value = item.company || '';
|
|
form.querySelector('[name="rating"]').value = item.rating;
|
|
form.querySelector('[name="text"]').value = item.text;
|
|
form.querySelector('[name="status"]').value = item.status;
|
|
|
|
// Restore photo preview
|
|
const photoArea = form.querySelector('.file-upload-area');
|
|
if (item.photo) {
|
|
photoArea.style.position = 'relative';
|
|
photoArea.innerHTML = `
|
|
<img src="/${item.photo}" alt="${item.name}" style="max-height:80px;border-radius:50%;margin-bottom:0.5rem;">
|
|
<p style="font-size:0.85rem;color:var(--gray-500);">Click to replace photo</p>
|
|
<input type="file" name="photo" accept="image/*" style="position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;">`;
|
|
} else {
|
|
resetPhotoArea(form);
|
|
}
|
|
|
|
document.getElementById('testimonial-modal').classList.add('active');
|
|
}
|
|
|
|
async function saveTestimonial() {
|
|
const form = document.getElementById('testimonial-form');
|
|
const formData = new FormData(form);
|
|
const id = form.dataset.id;
|
|
|
|
if (id) formData.append('id', id);
|
|
|
|
try {
|
|
const result = await MSPE.API.upload('testimonials.php', formData);
|
|
|
|
if (result && result.success) {
|
|
MSPE.showNotification(id ? 'Updated successfully' : 'Created successfully', 'success');
|
|
closeModal();
|
|
loadTestimonials();
|
|
} else {
|
|
MSPE.showNotification(result?.message || 'Error saving testimonial', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
MSPE.showNotification('Error saving testimonial', 'error');
|
|
}
|
|
}
|
|
|
|
async function updateStatus(id, status) {
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('id', id);
|
|
formData.append('status', status);
|
|
|
|
const result = await MSPE.API.upload('testimonials.php', formData);
|
|
|
|
if (result && result.success) {
|
|
MSPE.showNotification(`Testimonial ${status}`, 'success');
|
|
loadTestimonials();
|
|
} else {
|
|
MSPE.showNotification(result?.message || 'Error updating status', 'error');
|
|
}
|
|
} catch (error) {
|
|
MSPE.showNotification('Error updating status', 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteTestimonial(id) {
|
|
if (!await MSPE.confirmDialog('Delete this testimonial?')) return;
|
|
|
|
try {
|
|
const response = await MSPE.API.delete(`testimonials.php?id=${id}`);
|
|
if (response.success) {
|
|
MSPE.showNotification('Deleted successfully', 'success');
|
|
loadTestimonials();
|
|
}
|
|
} catch (error) {
|
|
MSPE.showNotification('Error deleting testimonial', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|