feat: Add complete reviews system with star ratings
✨ New Features: - Reviews system with 1-5 star ratings - User can add, edit, and delete their own reviews - One review per product per user (DB constraint) - Automatic average rating calculation - Review count tracking - Interactive star selection UI - AJAX-powered review submission - Responsive design for all devices 🗄️ Database: - New 'reviews' table with full structure - Added 'rating' and 'review_count' fields to products - PostgreSQL triggers for automatic rating updates - Database functions for rating calculations - Indexes for performance optimization 📦 Backend (PHP): - Review model with 15+ methods - ReviewController with 5 actions - Updated Product model to include ratings - Updated ProductController to load reviews - 5 new API endpoints 🎨 Frontend: - Reviews list component (_reviews_list.php) - Review form component (_review_form.php) - Reviews sechow page - Star ratings in catalog view - Interactive JavaScript (200+ lines) - Adaptive styles (400+ lines) 🔒 Security: - Server-side authorization checks - XSS protection (htmlspecialchars) - SQL injection protection (PDO prepared) - Input validation (client + server) - Access control for review editing 📝 Modified Files: - app/Models/Product.php - added rating fields to queries - app/Controllers/ProductController.php - loads reviews - app/Views/products/show.php - reviews section - app/Views/products/catalog.php - star ratings - config/routes.php - review endpoints - public/style_for_cite.less - rating styles 🆕 New Files: - app/Models/Review.php - app/Controllers/ReviewController.php - app/Views/products/_reviews_list.php - app/Views/products/_review_form.php
This commit is contained in:
@@ -38,7 +38,17 @@
|
||||
<body>
|
||||
<div id="notification" class="notification"></div>
|
||||
|
||||
<?= \App\Core\View::partial('header', ['user' => $user ?? null, 'isLoggedIn' => $isLoggedIn ?? \App\Core\View::isAuthenticated(), 'isAdmin' => $isAdmin ?? \App\Core\View::isAdmin()]) ?>
|
||||
<?php
|
||||
// Загружаем категории для header
|
||||
$categoryModel = new \App\Models\Category();
|
||||
$headerCategories = $categoryModel->getActive();
|
||||
?>
|
||||
<?= \App\Core\View::partial('header', [
|
||||
'user' => $user ?? null,
|
||||
'isLoggedIn' => $isLoggedIn ?? \App\Core\View::isAuthenticated(),
|
||||
'isAdmin' => $isAdmin ?? \App\Core\View::isAdmin(),
|
||||
'categories' => $headerCategories ?? []
|
||||
]) ?>
|
||||
|
||||
<main>
|
||||
<?= $content ?>
|
||||
@@ -55,6 +65,68 @@
|
||||
setTimeout(function() { notification.removeClass('show'); }, 3000);
|
||||
}
|
||||
|
||||
// Выпадающий список категорий
|
||||
(function() {
|
||||
function initCatalogDropdown() {
|
||||
var catalogDropdown = document.getElementById('catalogDropdown');
|
||||
var catalogMenu = document.getElementById('catalogMenu');
|
||||
|
||||
if (!catalogDropdown || !catalogMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Обработчик клика на выпадающий список
|
||||
catalogDropdown.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
var isActive = this.classList.contains('active');
|
||||
if (isActive) {
|
||||
this.classList.remove('active');
|
||||
catalogMenu.style.display = 'none';
|
||||
} else {
|
||||
this.classList.add('active');
|
||||
catalogMenu.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Закрытие при клике вне меню
|
||||
document.addEventListener('click', function(e) {
|
||||
if (catalogDropdown && !catalogDropdown.contains(e.target)) {
|
||||
catalogDropdown.classList.remove('active');
|
||||
if (catalogMenu) {
|
||||
catalogMenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Закрытие при клике на ссылку в меню (через делегирование)
|
||||
if (catalogMenu) {
|
||||
catalogMenu.addEventListener('click', function(e) {
|
||||
if (e.target.tagName === 'A') {
|
||||
catalogDropdown.classList.remove('active');
|
||||
catalogMenu.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация при загрузке DOM
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initCatalogDropdown);
|
||||
} else {
|
||||
initCatalogDropdown();
|
||||
}
|
||||
|
||||
// Резервная инициализация при полной загрузке страницы
|
||||
window.addEventListener('load', function() {
|
||||
var catalogDropdown = document.getElementById('catalogDropdown');
|
||||
if (catalogDropdown && !catalogDropdown.hasAttribute('data-initialized')) {
|
||||
catalogDropdown.setAttribute('data-initialized', 'true');
|
||||
initCatalogDropdown();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
$(document).ready(function() {
|
||||
$.get('/cart/count', function(response) {
|
||||
if (response.success) {
|
||||
|
||||
Reference in New Issue
Block a user