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:
kirill.khorkov
2026-01-06 17:04:09 +03:00
parent 547c561ed0
commit a4092adf2e
17 changed files with 1646 additions and 59 deletions

View File

@@ -3,22 +3,30 @@ $isLoggedIn = $isLoggedIn ?? \App\Core\View::isAuthenticated();
$isAdmin = $isAdmin ?? \App\Core\View::isAdmin();
$user = $user ?? \App\Core\View::currentUser();
?>
<style>
#catalogMenu {
display: none;
}
#catalogDropdown.active #catalogMenu {
display: block;
}
</style>
<header class="header">
<div class="header__top">
<div class="container header__top-content">
<a href="/" class="logo">AETERNA</a>
<div class="search-catalog">
<div class="catalog-dropdown">
<div class="catalog-dropdown" id="catalogDropdown">
Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu">
<div class="catalog-dropdown__menu" id="catalogMenu">
<ul>
<li><a href="/catalog">Все товары</a></li>
<li><a href="/catalog?category=1">Диваны</a></li>
<li><a href="/catalog?category=2">Кровати</a></li>
<li><a href="/catalog?category=3">Шкафы</a></li>
<li><a href="/catalog?category=4">Стулья</a></li>
<li><a href="/catalog?category=5">Столы</a></li>
<?php if (!empty($categories)): ?>
<?php foreach ($categories as $category): ?>
<li><a href="/catalog?category=<?= $category['category_id'] ?>"><?= htmlspecialchars($category['name']) ?></a></li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
</div>
@@ -34,48 +42,16 @@ $user = $user ?? \App\Core\View::currentUser();
<div class="header__icons--top">
<?php if ($isLoggedIn): ?>
<?php if (!$isAdmin): ?>
<a href="/cart" class="icon cart-icon">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
</a>
<?php endif; ?>
<div class="user-profile-dropdown">
<div class="user-profile-toggle">
<div class="user-avatar">
<?= !empty($user['email']) ? strtoupper(substr($user['email'], 0, 1)) : 'U' ?>
</div>
<div class="user-info">
<div class="user-email"><?= htmlspecialchars($user['email'] ?? '') ?></div>
<div class="user-status <?= $isAdmin ? 'admin' : 'user' ?>">
<?= $isAdmin ? 'Админ' : 'Пользователь' ?>
</div>
</div>
<i class="fas fa-chevron-down" style="font-size: 12px; color: #666;"></i>
</div>
<div class="user-profile-menu">
<div class="user-profile-header">
<div class="user-profile-name">
<i class="fas fa-user"></i>
<?= htmlspecialchars($user['full_name'] ?? $user['email'] ?? '') ?>
</div>
<div class="user-profile-details">
<small><i class="far fa-envelope"></i> <?= htmlspecialchars($user['email'] ?? '') ?></small>
<?php if (!empty($user['login_time'])): ?>
<br><small><i class="far fa-clock"></i> Вошел: <?= date('d.m.Y H:i', $user['login_time']) ?></small>
<?php endif; ?>
</div>
</div>
<ul class="user-profile-links">
<li><a href="/cart"><i class="fas fa-shopping-bag"></i> Корзина</a></li>
<?php if ($isAdmin): ?>
<li><a href="/admin"><i class="fas fa-user-shield"></i> Админ-панель</a></li>
<?php endif; ?>
<li><a href="/logout" class="logout-link"><i class="fas fa-sign-out-alt"></i> Выйти</a></li>
</ul>
</div>
</div>
<a href="/logout" style="font-size: 14px; color: #666; text-decoration: none; margin-left: 15px;">
<i class="fas fa-sign-out-alt"></i> Выйти
</a>
<?php else: ?>
<a href="/login" class="icon"><i class="far fa-user"></i></a>
<a href="/login" style="font-size: 12px; color: #666; margin-left: 5px;">Войти</a>