mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
282 lines
12 KiB
PHP
282 lines
12 KiB
PHP
<?php
|
|
ob_start();
|
|
/**
|
|
* Tom's Java Jive - Admin Reviews
|
|
*/
|
|
|
|
$pageTitle = 'Reviews';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
// Handle actions
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
$reviewId = $_POST['review_id'] ?? '';
|
|
|
|
if ($action === 'approve' && $reviewId) {
|
|
db()->update('reviews', ['is_approved' => 1], 'review_id = :id', ['id' => $reviewId]);
|
|
setFlash('success', 'Review approved');
|
|
}
|
|
|
|
if ($action === 'reject' && $reviewId) {
|
|
db()->update('reviews', ['is_approved' => 0], 'review_id = :id', ['id' => $reviewId]);
|
|
setFlash('success', 'Review rejected');
|
|
}
|
|
|
|
if ($action === 'update' && $reviewId) {
|
|
$rating = max(1, min(5, intval($_POST['rating'] ?? 5)));
|
|
$title = trim($_POST['title'] ?? '');
|
|
$comment = trim($_POST['comment'] ?? '');
|
|
db()->update('reviews', [
|
|
'rating' => $rating,
|
|
'title' => $title ?: null,
|
|
'comment' => $comment ?: null,
|
|
], 'review_id = :id', ['id' => $reviewId]);
|
|
setFlash('success', 'Review updated');
|
|
}
|
|
|
|
if ($action === 'delete' && $reviewId) {
|
|
db()->delete('reviews', 'review_id = :id', ['id' => $reviewId]);
|
|
setFlash('success', 'Review deleted');
|
|
}
|
|
|
|
header('Location: /admin/reviews.php');
|
|
exit;
|
|
}
|
|
|
|
// Filters
|
|
$status = $_GET['status'] ?? '';
|
|
$rating = $_GET['rating'] ?? '';
|
|
|
|
$where = ['1=1'];
|
|
$params = [];
|
|
|
|
if ($status === 'pending') {
|
|
$where[] = 'is_approved = 0';
|
|
} elseif ($status === 'approved') {
|
|
$where[] = 'is_approved = 1';
|
|
}
|
|
|
|
if ($rating) {
|
|
$where[] = 'rating = :rating';
|
|
$params['rating'] = $rating;
|
|
}
|
|
|
|
$whereClause = implode(' AND ', $where);
|
|
|
|
$reviews = db()->fetchAll(
|
|
"SELECT r.*, p.name as product_name FROM reviews r
|
|
LEFT JOIN products p ON r.product_id = p.product_id
|
|
WHERE {$whereClause} ORDER BY r.created_at DESC LIMIT 100",
|
|
$params
|
|
);
|
|
|
|
// Stats
|
|
$totalReviews = db()->count('reviews');
|
|
$pendingReviews = db()->count('reviews', 'is_approved = 0');
|
|
$avgRating = db()->fetch("SELECT AVG(rating) as avg FROM reviews WHERE is_approved = 1")['avg'] ?? 0;
|
|
?>
|
|
|
|
<div class="page-header">
|
|
<h1 class="page-title">Product Reviews</h1>
|
|
</div>
|
|
|
|
<?php if (hasFlash('success')): ?>
|
|
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= getFlash('success') ?></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats-grid" style="margin-bottom: 1.5rem;">
|
|
<div class="stat-card">
|
|
<div class="stat-card-icon primary"><i class="fas fa-star"></i></div>
|
|
<div>
|
|
<div class="stat-card-value"><?= number_format($avgRating, 1) ?></div>
|
|
<div class="stat-card-label">Average Rating</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-icon success"><i class="fas fa-comments"></i></div>
|
|
<div>
|
|
<div class="stat-card-value"><?= $totalReviews ?></div>
|
|
<div class="stat-card-label">Total Reviews</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-icon <?= $pendingReviews > 0 ? 'warning' : 'success' ?>"><i class="fas fa-clock"></i></div>
|
|
<div>
|
|
<div class="stat-card-value"><?= $pendingReviews ?></div>
|
|
<div class="stat-card-label">Pending Approval</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="admin-card">
|
|
<div class="admin-card-body">
|
|
<form method="GET" style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: end;">
|
|
<div class="form-group mb-0">
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="form-select">
|
|
<option value="">All</option>
|
|
<option value="pending" <?= $status === 'pending' ? 'selected' : '' ?>>Pending</option>
|
|
<option value="approved" <?= $status === 'approved' ? 'selected' : '' ?>>Approved</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group mb-0">
|
|
<label class="form-label">Rating</label>
|
|
<select name="rating" class="form-select">
|
|
<option value="">All Ratings</option>
|
|
<?php for ($i = 5; $i >= 1; $i--): ?>
|
|
<option value="<?= $i ?>" <?= $rating == $i ? 'selected' : '' ?>><?= $i ?> Star<?= $i > 1 ? 's' : '' ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-secondary"><i class="fas fa-filter"></i> Filter</button>
|
|
<?php if ($status || $rating): ?>
|
|
<a href="/admin/reviews.php" class="btn btn-secondary">Clear</a>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reviews List -->
|
|
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
<?php if (empty($reviews)): ?>
|
|
<div class="admin-card">
|
|
<div class="admin-card-body text-center text-muted" style="padding: 3rem;">
|
|
No reviews found
|
|
</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($reviews as $review): ?>
|
|
<div class="admin-card">
|
|
<div class="admin-card-body">
|
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
|
|
<div>
|
|
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
<strong><?= htmlspecialchars($review['customer_name'] ?? 'Anonymous') ?></strong>
|
|
<?php if ($review['is_verified_purchase']): ?>
|
|
<span class="badge badge-success">Verified Purchase</span>
|
|
<?php endif; ?>
|
|
<?php if (!$review['is_approved']): ?>
|
|
<span class="badge badge-warning">Pending</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="text-muted" style="font-size: 0.875rem;">
|
|
on <a href="/admin/product-edit.php?id=<?= $review['product_id'] ?>"><?= htmlspecialchars($review['product_name'] ?? 'Unknown Product') ?></a>
|
|
• <?= formatDate($review['created_at']) ?>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<?php if (!$review['is_approved']): ?>
|
|
<form method="POST" style="display: inline;">
|
|
<input type="hidden" name="action" value="approve">
|
|
<input type="hidden" name="review_id" value="<?= $review['review_id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-success"><i class="fas fa-check"></i> Approve</button>
|
|
</form>
|
|
<?php else: ?>
|
|
<form method="POST" style="display: inline;">
|
|
<input type="hidden" name="action" value="reject">
|
|
<input type="hidden" name="review_id" value="<?= $review['review_id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-secondary"><i class="fas fa-times"></i> Unapprove</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<button type="button" class="btn btn-sm btn-secondary" title="Edit"
|
|
onclick='openEditReview(<?= json_encode(['review_id' => $review['review_id'], 'rating' => $review['rating'], 'title' => $review['title'], 'comment' => $review['comment']]) ?>)'>
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<form method="POST" style="display: inline;">
|
|
<input type="hidden" name="action" value="delete">
|
|
<input type="hidden" name="review_id" value="<?= $review['review_id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Delete this review?"><i class="fas fa-trash"></i></button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 0.75rem;">
|
|
<?php for ($i = 1; $i <= 5; $i++): ?>
|
|
<i class="fas fa-star" style="color: <?= $i <= $review['rating'] ? '#F59E0B' : '#E5E7EB' ?>;"></i>
|
|
<?php endfor; ?>
|
|
</div>
|
|
|
|
<?php if ($review['title']): ?>
|
|
<h4 style="margin-bottom: 0.5rem;"><?= htmlspecialchars($review['title']) ?></h4>
|
|
<?php endif; ?>
|
|
|
|
<p style="margin: 0; color: var(--admin-text);">
|
|
<?= nl2br(htmlspecialchars($review['comment'])) ?>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Edit Review Modal -->
|
|
<div class="modal-overlay" id="editReviewModal">
|
|
<div class="modal" style="max-width:560px;width:95vw">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Edit Review</h3>
|
|
<button type="button" class="modal-close" onclick="Modal.close('editReviewModal')">×</button>
|
|
</div>
|
|
<form method="POST" id="editReviewForm">
|
|
<div class="modal-body">
|
|
<input type="hidden" name="action" value="update">
|
|
<input type="hidden" name="review_id" id="editReviewId">
|
|
<div class="form-group">
|
|
<label class="form-label">Rating</label>
|
|
<div id="starRating" style="display:flex;gap:.25rem;font-size:1.5rem;cursor:pointer;margin-bottom:.25rem">
|
|
<?php for ($s = 1; $s <= 5; $s++): ?>
|
|
<i class="fas fa-star" data-star="<?= $s ?>" style="color:#E5E7EB"></i>
|
|
<?php endfor; ?>
|
|
</div>
|
|
<input type="hidden" name="rating" id="editRating" value="5">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Title</label>
|
|
<input type="text" name="title" id="editTitle" class="form-input" placeholder="Review title (optional)">
|
|
</div>
|
|
<div class="form-group mb-0">
|
|
<label class="form-label">Comment</label>
|
|
<textarea name="comment" id="editComment" class="form-input" rows="5" style="resize:vertical"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="Modal.close('editReviewModal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openEditReview(review) {
|
|
document.getElementById('editReviewId').value = review.review_id;
|
|
document.getElementById('editTitle').value = review.title || '';
|
|
document.getElementById('editComment').value = review.comment || '';
|
|
setStarRating(review.rating || 5);
|
|
Modal.open('editReviewModal');
|
|
}
|
|
|
|
function setStarRating(val) {
|
|
document.getElementById('editRating').value = val;
|
|
document.querySelectorAll('#starRating i').forEach(function(star) {
|
|
star.style.color = parseInt(star.dataset.star) <= val ? '#F59E0B' : '#E5E7EB';
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('#starRating i').forEach(function(star) {
|
|
star.addEventListener('click', function() { setStarRating(parseInt(this.dataset.star)); });
|
|
star.addEventListener('mouseenter', function() {
|
|
var val = parseInt(this.dataset.star);
|
|
document.querySelectorAll('#starRating i').forEach(function(s) {
|
|
s.style.color = parseInt(s.dataset.star) <= val ? '#F59E0B' : '#E5E7EB';
|
|
});
|
|
});
|
|
star.addEventListener('mouseleave', function() {
|
|
setStarRating(parseInt(document.getElementById('editRating').value));
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|