[MVC] Полная миграция на MVC архитектуру

- Создано ядро MVC: App, Router, Controller, Model, View, Database
- Созданы модели: User, Product, Category, Cart, Order
- Созданы контроллеры: Home, Auth, Product, Cart, Order, Page, Admin
- Созданы layouts и partials для представлений
- Добавлены все views для страниц
- Настроена маршрутизация с чистыми URL
- Обновлена конфигурация Docker и Apache для mod_rewrite
- Добавлена единая точка входа public/index.php
This commit is contained in:
kirill.khorkov
2026-01-03 11:48:14 +03:00
parent 3f257120fa
commit d2c15ec37f
53 changed files with 8650 additions and 30 deletions

View File

@@ -0,0 +1,56 @@
<?php $isEdit = $action === 'edit'; ?>
<h2><?= $isEdit ? 'Редактирование категории' : 'Добавление категории' ?></h2>
<a href="/cite_practica/admin/categories" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к списку
</a>
<div class="form-container">
<form action="/cite_practica/admin/categories/<?= $isEdit ? 'edit/' . $category['category_id'] : 'add' ?>" method="POST">
<div class="form-group">
<label>Название категории *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($category['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Родительская категория</label>
<select name="parent_id" class="form-control">
<option value="">Нет (корневая категория)</option>
<?php foreach ($parentCategories as $parent): ?>
<?php if (!$isEdit || $parent['category_id'] != $category['category_id']): ?>
<option value="<?= $parent['category_id'] ?>"
<?= ($category['parent_id'] ?? 0) == $parent['category_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($parent['name']) ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($category['description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label>Порядок сортировки</label>
<input type="number" name="sort_order" class="form-control"
value="<?= $category['sort_order'] ?? 0 ?>">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" value="1"
<?= ($category['is_active'] ?? true) ? 'checked' : '' ?>>
Категория активна
</label>
</div>
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> <?= $isEdit ? 'Сохранить изменения' : 'Добавить категорию' ?>
</button>
</form>
</div>

View File

@@ -0,0 +1,60 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление категориями</h2>
<a href="/cite_practica/admin/categories/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить категорию
</a>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Родитель</th>
<th>Товаров</th>
<th>Порядок</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr style="<?= !$category['is_active'] ? 'opacity: 0.5;' : '' ?>">
<td><?= $category['category_id'] ?></td>
<td><?= htmlspecialchars($category['name']) ?></td>
<td><?= htmlspecialchars($category['parent_name'] ?? '-') ?></td>
<td><?= $category['product_count'] ?></td>
<td><?= $category['sort_order'] ?></td>
<td>
<?php if ($category['is_active']): ?>
<span style="color: green;"><i class="fas fa-check"></i> Активна</span>
<?php else: ?>
<span style="color: red;"><i class="fas fa-times"></i> Скрыта</span>
<?php endif; ?>
</td>
<td>
<div class="action-buttons">
<a href="/cite_practica/admin/categories/edit/<?= $category['category_id'] ?>" class="btn btn-sm btn-warning" title="Редактировать">
<i class="fas fa-edit"></i>
</a>
<form action="/cite_practica/admin/categories/delete/<?= $category['category_id'] ?>" method="POST" style="display: inline;"
onsubmit="return confirm('Удалить категорию?');">
<button type="submit" class="btn btn-sm btn-danger" title="Удалить">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,45 @@
<?php $action = 'dashboard'; ?>
<h2>Дашборд</h2>
<div class="stats-grid">
<div class="stat-card">
<h3><?= $stats['total_products'] ?></h3>
<p>Всего товаров</p>
</div>
<div class="stat-card">
<h3><?= $stats['active_products'] ?></h3>
<p>Активных товаров</p>
</div>
<div class="stat-card">
<h3><?= $stats['total_orders'] ?></h3>
<p>Заказов</p>
</div>
<div class="stat-card">
<h3><?= $stats['total_users'] ?></h3>
<p>Пользователей</p>
</div>
<div class="stat-card">
<h3><?= number_format($stats['revenue'], 0, '', ' ') ?> ₽</h3>
<p>Выручка</p>
</div>
</div>
<div style="margin-top: 30px;">
<h3>Быстрые действия</h3>
<div style="display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap;">
<a href="/cite_practica/admin/products/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить товар
</a>
<a href="/cite_practica/admin/categories/add" class="btn btn-primary">
<i class="fas fa-plus"></i> Добавить категорию
</a>
<a href="/cite_practica/admin/orders" class="btn btn-primary">
<i class="fas fa-shopping-cart"></i> Просмотреть заказы
</a>
<a href="/cite_practica/catalog" class="btn btn-primary">
<i class="fas fa-store"></i> Перейти в каталог
</a>
</div>
</div>

View File

@@ -0,0 +1,74 @@
<?php use App\Core\View; ?>
<a href="/cite_practica/admin/orders" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к заказам
</a>
<h2>Заказ <?= htmlspecialchars($order['order_number']) ?></h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<div class="form-container">
<h3>Информация о заказе</h3>
<table style="border: none;">
<tr><td><strong>Дата:</strong></td><td><?= View::formatDateTime($order['created_at']) ?></td></tr>
<tr><td><strong>Покупатель:</strong></td><td><?= htmlspecialchars($order['customer_name']) ?></td></tr>
<tr><td><strong>Email:</strong></td><td><?= htmlspecialchars($order['customer_email']) ?></td></tr>
<tr><td><strong>Телефон:</strong></td><td><?= htmlspecialchars($order['customer_phone']) ?></td></tr>
<tr><td><strong>Адрес:</strong></td><td><?= htmlspecialchars($order['delivery_address']) ?></td></tr>
<tr><td><strong>Способ доставки:</strong></td><td><?= htmlspecialchars($order['delivery_method']) ?></td></tr>
<tr><td><strong>Способ оплаты:</strong></td><td><?= htmlspecialchars($order['payment_method']) ?></td></tr>
</table>
</div>
<div class="form-container">
<h3>Статус заказа</h3>
<form action="/cite_practica/admin/orders/<?= $order['order_id'] ?>/status" method="POST">
<div class="form-group">
<select name="status" class="form-control">
<option value="pending" <?= $order['status'] === 'pending' ? 'selected' : '' ?>>Ожидает</option>
<option value="processing" <?= $order['status'] === 'processing' ? 'selected' : '' ?>>В обработке</option>
<option value="shipped" <?= $order['status'] === 'shipped' ? 'selected' : '' ?>>Отправлен</option>
<option value="completed" <?= $order['status'] === 'completed' ? 'selected' : '' ?>>Завершен</option>
<option value="cancelled" <?= $order['status'] === 'cancelled' ? 'selected' : '' ?>>Отменен</option>
</select>
</div>
<button type="submit" class="btn btn-success">Обновить статус</button>
</form>
<h3 style="margin-top: 20px;">Итого</h3>
<table style="border: none;">
<tr><td>Товары:</td><td><?= View::formatPrice($order['subtotal']) ?></td></tr>
<tr><td>Скидка:</td><td>-<?= View::formatPrice($order['discount_amount']) ?></td></tr>
<tr><td>Доставка:</td><td><?= View::formatPrice($order['delivery_price']) ?></td></tr>
<tr style="font-weight: bold;"><td>ИТОГО:</td><td><?= View::formatPrice($order['final_amount']) ?></td></tr>
</table>
</div>
</div>
<h3 style="margin-top: 30px;">Товары в заказе</h3>
<table>
<thead>
<tr>
<th>Изображение</th>
<th>Товар</th>
<th>Цена</th>
<th>Кол-во</th>
<th>Сумма</th>
</tr>
</thead>
<tbody>
<?php foreach ($order['items'] as $item): ?>
<tr>
<td>
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td>
<td><?= htmlspecialchars($item['product_name']) ?></td>
<td><?= View::formatPrice($item['product_price']) ?></td>
<td><?= $item['quantity'] ?></td>
<td><?= View::formatPrice($item['total_price']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,62 @@
<?php use App\Core\View; ?>
<h2>Заказы</h2>
<?php if (empty($orders)): ?>
<div class="alert">Заказы отсутствуют</div>
<?php else: ?>
<table>
<thead>
<tr>
<th>№ заказа</th>
<th>Дата</th>
<th>Покупатель</th>
<th>Сумма</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order): ?>
<tr>
<td><strong><?= htmlspecialchars($order['order_number']) ?></strong></td>
<td><?= View::formatDateTime($order['created_at']) ?></td>
<td>
<?= htmlspecialchars($order['customer_name']) ?><br>
<small><?= htmlspecialchars($order['user_email']) ?></small>
</td>
<td><?= View::formatPrice($order['final_amount']) ?></td>
<td>
<?php
$statusColors = [
'pending' => '#ffc107',
'processing' => '#17a2b8',
'shipped' => '#007bff',
'completed' => '#28a745',
'cancelled' => '#dc3545'
];
$statusNames = [
'pending' => 'Ожидает',
'processing' => 'Обработка',
'shipped' => 'Отправлен',
'completed' => 'Завершен',
'cancelled' => 'Отменен'
];
$color = $statusColors[$order['status']] ?? '#666';
$name = $statusNames[$order['status']] ?? $order['status'];
?>
<span style="background: <?= $color ?>; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<?= $name ?>
</span>
</td>
<td>
<a href="/cite_practica/admin/orders/<?= $order['order_id'] ?>" class="btn btn-sm btn-primary">
<i class="fas fa-eye"></i> Подробнее
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>

View File

@@ -0,0 +1,106 @@
<?php $isEdit = $action === 'edit'; ?>
<h2><?= $isEdit ? 'Редактирование товара' : 'Добавление товара' ?></h2>
<a href="/cite_practica/admin/products" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к списку
</a>
<div class="form-container">
<form action="/cite_practica/admin/products/<?= $isEdit ? 'edit/' . $product['product_id'] : 'add' ?>" method="POST">
<div class="form-group">
<label>Название товара *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($product['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Категория *</label>
<select name="category_id" class="form-control" required>
<option value="">Выберите категорию</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['category_id'] ?>"
<?= ($product['category_id'] ?? 0) == $cat['category_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="4"><?= htmlspecialchars($product['description'] ?? '') ?></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Цена *</label>
<input type="number" name="price" class="form-control" step="0.01"
value="<?= $product['price'] ?? '' ?>" required>
</div>
<div class="form-group">
<label>Старая цена (для скидки)</label>
<input type="number" name="old_price" class="form-control" step="0.01"
value="<?= $product['old_price'] ?? '' ?>">
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Артикул (SKU)</label>
<input type="text" name="sku" class="form-control"
value="<?= htmlspecialchars($product['sku'] ?? '') ?>"
placeholder="Автоматически, если пусто">
</div>
<div class="form-group">
<label>Количество на складе *</label>
<input type="number" name="stock_quantity" class="form-control"
value="<?= $product['stock_quantity'] ?? 0 ?>" required>
</div>
</div>
<div class="form-group">
<label>URL изображения</label>
<input type="text" name="image_url" class="form-control"
value="<?= htmlspecialchars($product['image_url'] ?? '') ?>"
placeholder="img/название.jpg">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Цвет</label>
<input type="text" name="color" class="form-control"
value="<?= htmlspecialchars($product['color'] ?? '') ?>">
</div>
<div class="form-group">
<label>Материал</label>
<input type="text" name="material" class="form-control"
value="<?= htmlspecialchars($product['material'] ?? '') ?>">
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_available" value="1"
<?= ($product['is_available'] ?? true) ? 'checked' : '' ?>>
Товар доступен для покупки
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_featured" value="1"
<?= ($product['is_featured'] ?? false) ? 'checked' : '' ?>>
Рекомендуемый товар
</label>
</div>
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> <?= $isEdit ? 'Сохранить изменения' : 'Добавить товар' ?>
</button>
</form>
</div>

View File

@@ -0,0 +1,75 @@
<?php use App\Core\View; ?>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление товарами</h2>
<a href="/cite_practica/admin/products/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить товар
</a>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div style="margin-bottom: 15px;">
<a href="/cite_practica/admin/products" class="btn btn-sm <?= !$showAll ? 'btn-primary' : '' ?>">Активные</a>
<a href="/cite_practica/admin/products?show_all=1" class="btn btn-sm <?= $showAll ? 'btn-primary' : '' ?>">Все товары</a>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Изображение</th>
<th>Название</th>
<th>Категория</th>
<th>Цена</th>
<th>На складе</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr style="<?= !$product['is_available'] ? 'opacity: 0.5;' : '' ?>">
<td><?= $product['product_id'] ?></td>
<td>
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td>
<td><?= htmlspecialchars($product['name']) ?></td>
<td><?= htmlspecialchars($product['category_name'] ?? 'Без категории') ?></td>
<td><?= View::formatPrice($product['price']) ?></td>
<td><?= $product['stock_quantity'] ?> шт.</td>
<td>
<?php if ($product['is_available']): ?>
<span style="color: green;"><i class="fas fa-check"></i> Активен</span>
<?php else: ?>
<span style="color: red;"><i class="fas fa-times"></i> Скрыт</span>
<?php endif; ?>
</td>
<td>
<div class="action-buttons">
<a href="/cite_practica/product/<?= $product['product_id'] ?>" class="btn btn-sm btn-primary" title="Просмотр">
<i class="fas fa-eye"></i>
</a>
<a href="/cite_practica/admin/products/edit/<?= $product['product_id'] ?>" class="btn btn-sm btn-warning" title="Редактировать">
<i class="fas fa-edit"></i>
</a>
<form action="/cite_practica/admin/products/delete/<?= $product['product_id'] ?>" method="POST" style="display: inline;"
onsubmit="return confirm('Скрыть товар?');">
<button type="submit" class="btn btn-sm btn-danger" title="Скрыть">
<i class="fas fa-eye-slash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,43 @@
<?php use App\Core\View; ?>
<h2>Пользователи</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>ФИО</th>
<th>Email</th>
<th>Телефон</th>
<th>Город</th>
<th>Роль</th>
<th>Регистрация</th>
<th>Последний вход</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td><?= $u['user_id'] ?></td>
<td><?= htmlspecialchars($u['full_name'] ?? '-') ?></td>
<td><?= htmlspecialchars($u['email']) ?></td>
<td><?= htmlspecialchars($u['phone'] ?? '-') ?></td>
<td><?= htmlspecialchars($u['city'] ?? '-') ?></td>
<td>
<?php if ($u['is_admin']): ?>
<span style="background: #617365; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<i class="fas fa-user-shield"></i> Админ
</span>
<?php else: ?>
<span style="background: #28a745; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<i class="fas fa-user"></i> Пользователь
</span>
<?php endif; ?>
</td>
<td><?= $u['created_at'] ? View::formatDateTime($u['created_at']) : '-' ?></td>
<td><?= $u['last_login'] ? View::formatDateTime($u['last_login']) : 'Не было' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

91
app/Views/auth/login.php Normal file
View File

@@ -0,0 +1,91 @@
<?php $title = 'Вход'; ?>
<main class="profile-page-main">
<div class="profile-container">
<div class="profile-left-col">
<div class="logo">AETERNA</div>
</div>
<div class="profile-right-col">
<div class="profile-form-block">
<h2>ВХОД В АККАУНТ</h2>
<?php if (!empty($error)): ?>
<div style="background: #f8d7da; color: #721c24; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
<?= htmlspecialchars($success) ?>
</div>
<?php endif; ?>
<form class="profile-form" id="loginForm">
<input type="hidden" name="redirect" value="<?= htmlspecialchars($redirect ?? '/catalog') ?>">
<div class="input-group">
<label for="login-email">E-mail</label>
<input type="email" id="login-email" name="email" placeholder="Ваш электронный адрес" required>
</div>
<div class="input-group">
<label for="login-password">Пароль</label>
<input type="password" id="login-password" name="password" placeholder="Введите пароль" required>
</div>
<div class="form-options">
<label class="remember-me">
<input type="checkbox" id="remember" name="remember">
Запомнить меня
</label>
<a href="#" class="forgot-password">Забыли пароль?</a>
</div>
<button type="submit" class="btn primary-btn save-btn">Войти</button>
<div class="auth-actions">
<span class="auth-text">Нет аккаунта?</span>
<a href="/cite_practica/register" class="login-btn">Зарегистрироваться</a>
</div>
</form>
</div>
</div>
</div>
</main>
<script>
$(document).ready(function() {
$('#loginForm').on('submit', function(e) {
e.preventDefault();
const email = $('#login-email').val();
const password = $('#login-password').val();
const redirect = $('input[name="redirect"]').val();
if (!email || !password) {
alert('Заполните все поля');
return;
}
$.ajax({
url: '/cite_practica/login',
method: 'POST',
data: { email: email, password: password, redirect: redirect },
dataType: 'json',
success: function(result) {
if (result.success) {
window.location.href = result.redirect || '/cite_practica/catalog';
} else {
alert(result.message || 'Ошибка авторизации');
}
},
error: function() {
alert('Ошибка сервера. Попробуйте позже.');
}
});
});
});
</script>

View File

@@ -0,0 +1,95 @@
<?php $title = 'Регистрация'; ?>
<main class="profile-page-main">
<?php if (!empty($errors)): ?>
<div style="background: #f8d7da; color: #721c24; padding: 15px; border-radius: 5px; margin: 20px auto; max-width: 800px;">
<h4><i class="fas fa-exclamation-circle"></i> Ошибки регистрации:</h4>
<ul>
<?php foreach ($errors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; margin: 20px auto; max-width: 800px;">
<i class="fas fa-check-circle"></i> <?= htmlspecialchars($success) ?>
</div>
<?php endif; ?>
<div style="background: #e8f4fd; padding: 15px; border-radius: 5px; margin: 20px auto; max-width: 800px; text-align: center; font-size: 14px; color: #0c5460;">
<i class="fas fa-info-circle"></i> Для доступа к каталогу и оформления заказов необходимо зарегистрироваться
</div>
<div class="profile-container">
<div class="profile-left-col">
<div class="logo" style="color: white;">AETERNA</div>
<div style="margin-top: 30px; color: rgba(255,255,255,0.8);">
<h3 style="margin-bottom: 15px; font-size: 18px;">Присоединяйтесь к нам</h3>
<p style="font-size: 14px; line-height: 1.5;">Создайте аккаунт чтобы получить доступ ко всем функциям:</p>
<ul style="margin-top: 15px; padding-left: 20px; font-size: 13px;">
<li>Доступ к каталогу товаров</li>
<li>Добавление товаров в корзину</li>
<li>Оформление заказов</li>
<li>История покупок</li>
</ul>
</div>
</div>
<div class="profile-right-col">
<div class="profile-form-block">
<h2>РЕГИСТРАЦИЯ</h2>
<form class="profile-form" action="/cite_practica/register" method="POST" id="registrationForm">
<div class="input-group">
<label for="fio">ФИО *</label>
<input type="text" id="fio" name="fio" placeholder="Введите ваше ФИО"
value="<?= htmlspecialchars($old['fio'] ?? '') ?>" required>
</div>
<div class="input-group">
<label for="city">Город *</label>
<input type="text" id="city" name="city" placeholder="Укажите ваш город"
value="<?= htmlspecialchars($old['city'] ?? '') ?>" required>
</div>
<div class="input-group">
<label for="email">E-mail *</label>
<input type="email" id="email" name="email" placeholder="Ваш электронный адрес"
value="<?= htmlspecialchars($old['email'] ?? '') ?>" required>
</div>
<div class="input-group">
<label for="phone">Телефон *</label>
<input type="tel" id="phone" name="phone" placeholder="+7(912)999-12-23"
value="<?= htmlspecialchars($old['phone'] ?? '') ?>" required>
</div>
<div class="input-group">
<label for="password">Пароль *</label>
<input type="password" id="password" name="password" placeholder="Минимум 6 символов" required>
</div>
<div class="input-group">
<label for="confirm-password">Подтвердите пароль *</label>
<input type="password" id="confirm-password" name="confirm-password" placeholder="Повторите пароль" required>
</div>
<div class="privacy-checkbox">
<label>
<input type="checkbox" id="privacy" name="privacy" required>
Я соглашаюсь с условиями обработки персональных данных *
</label>
</div>
<a href="/cite_practica/login" style="display: block; margin: 15px 0; text-align: center; color: #453227;">
Уже есть аккаунт? Войти
</a>
<button type="submit" class="btn primary-btn save-btn">Зарегистрироваться</button>
</form>
</div>
</div>
</div>
</main>

304
app/Views/cart/checkout.php Normal file
View File

@@ -0,0 +1,304 @@
<?php
$title = 'Корзина';
use App\Core\View;
?>
<style>
.main__content { display: flex; gap: 30px; margin: 30px 0; }
.products { flex: 1; }
.order { width: 400px; background: #f8f9fa; padding: 25px; border-radius: 8px; height: fit-content; }
.products__list { display: flex; flex-direction: column; gap: 15px; }
.products__item { display: flex; gap: 15px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.products__image { width: 100px; height: 100px; flex-shrink: 0; }
.products__image img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
.products__details { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
.products__name { font-weight: bold; color: #453227; }
.products__price { color: #617365; font-size: 18px; font-weight: bold; }
.products__controls { display: flex; align-items: center; gap: 15px; }
.products__quantity { display: flex; align-items: center; gap: 8px; }
.products__qty-btn { width: 30px; height: 30px; border: 1px solid #ddd; background: white; cursor: pointer; border-radius: 4px; }
.products__qty-value { padding: 5px 10px; }
.products__cart-icon { background: none; border: none; color: #dc3545; cursor: pointer; font-size: 18px; }
.empty-cart { text-align: center; padding: 40px; color: #666; }
.order__title { color: #453227; margin-bottom: 20px; }
.order__section { margin-bottom: 20px; }
.order__section-title { color: #453227; font-size: 14px; margin-bottom: 10px; }
.form__radio-group { display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px; }
.form__radio-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.form__input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; }
.form__row { display: flex; gap: 10px; }
.summary { margin: 20px 0; }
.summary__item { display: flex; justify-content: space-between; padding: 8px 0; }
.summary__item.total { font-weight: bold; font-size: 18px; border-top: 2px solid #453227; margin-top: 10px; padding-top: 15px; }
.order-btn { width: 100%; background: #453227; color: white; padding: 15px; border: none; border-radius: 4px; font-size: 16px; font-weight: bold; cursor: pointer; }
.order-btn:hover { background: #617365; }
.promo { display: flex; gap: 10px; margin-bottom: 20px; }
.promo__input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.promo__btn { background: #617365; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; }
.privacy { display: flex; align-items: center; gap: 8px; margin-top: 15px; font-size: 14px; color: #666; }
</style>
<main class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Корзина</span>
</div>
<h2 style="color: #453227; margin: 20px 0;">Товары в корзине</h2>
<?php if (empty($cartItems)): ?>
<div class="empty-cart">
<i class="fas fa-shopping-cart" style="font-size: 48px; color: #ccc; margin-bottom: 20px;"></i>
<p>Ваша корзина пуста</p>
<a href="/cite_practica/catalog" class="btn primary-btn" style="margin-top: 20px; display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
Продолжить покупки
</a>
</div>
<?php else: ?>
<div class="main__content">
<section class="products">
<div class="products__list" id="cartItems">
<?php foreach ($cartItems as $item): ?>
<div class="products__item" data-product-id="<?= $item['product_id'] ?>" data-price="<?= $item['price'] ?>">
<div class="products__image">
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
alt="<?= htmlspecialchars($item['name']) ?>">
</div>
<div class="products__details">
<div class="products__name"><?= htmlspecialchars($item['name']) ?></div>
<div class="products__price"><?= View::formatPrice($item['price']) ?></div>
<div class="products__controls">
<div class="products__quantity">
<button class="products__qty-btn minus" data-id="<?= $item['product_id'] ?>">-</button>
<span class="products__qty-value"><?= $item['quantity'] ?></span>
<button class="products__qty-btn plus" data-id="<?= $item['product_id'] ?>">+</button>
</div>
<button class="products__cart-icon remove-from-cart" data-id="<?= $item['product_id'] ?>">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<section class="order">
<form id="orderForm">
<h2 class="order__title">Оформление заказа</h2>
<div class="order__section">
<h3 class="order__section-title">СПОСОБ ДОСТАВКИ</h3>
<div class="form__radio-group">
<label class="form__radio-label">
<input type="radio" name="delivery" value="courier" checked>
Курьерская доставка
</label>
<label class="form__radio-label">
<input type="radio" name="delivery" value="pickup">
Самовывоз
</label>
</div>
<input type="text" name="full_name" class="form__input" placeholder="ФИО..."
value="<?= htmlspecialchars($user['full_name'] ?? '') ?>" required>
<div class="form__row">
<input type="tel" name="phone" class="form__input" placeholder="Телефон..."
value="<?= htmlspecialchars($user['phone'] ?? '') ?>" required style="flex: 1;">
<input type="email" name="email" class="form__input" placeholder="E-mail..."
value="<?= htmlspecialchars($user['email'] ?? '') ?>" required style="flex: 1;">
</div>
</div>
<div class="order__section">
<h3 class="order__section-title">СПОСОБ ОПЛАТЫ</h3>
<div class="form__radio-group">
<label class="form__radio-label">
<input type="radio" name="payment" value="card" checked>
Банковская карта
</label>
<label class="form__radio-label">
<input type="radio" name="payment" value="cash">
Наличные
</label>
</div>
<div class="form__row">
<input type="text" name="region" class="form__input" placeholder="Регион..." required style="flex: 1;">
<input type="text" name="postal_code" class="form__input" placeholder="Индекс..." style="flex: 1;">
</div>
<input type="text" name="address" class="form__input" placeholder="Улица, дом, квартира..." required>
</div>
<div class="promo">
<input type="text" id="promo_code" name="promo_code" class="promo__input" placeholder="Промокод">
<button type="button" class="promo__btn" id="applyPromo">ПРИМЕНИТЬ</button>
</div>
<input type="hidden" name="discount" value="0">
<input type="hidden" name="delivery_price" value="2000">
<div class="summary">
<div class="summary__item">
<span>Товары, <span class="summary-count"><?= $totalQuantity ?></span> шт.</span>
<span class="products-total"><?= View::formatPrice($totalAmount) ?></span>
</div>
<div class="summary__item">
<span>Скидка</span>
<span class="discount-total">0 ₽</span>
</div>
<div class="summary__item">
<span>Доставка</span>
<span class="delivery-price">2 000 ₽</span>
</div>
<div class="summary__item total">
<span>ИТОГО:</span>
<span class="final-total"><?= View::formatPrice($totalAmount + 2000) ?></span>
</div>
</div>
<button type="submit" class="order-btn" id="submit-order">ОФОРМИТЬ ЗАКАЗ</button>
<label class="privacy">
<input type="checkbox" id="privacy-checkbox" required>
Даю согласие на обработку персональных данных
</label>
</form>
</section>
</div>
<?php endif; ?>
</main>
<script>
$(document).ready(function() {
// Обновление количества
$('.products__qty-btn').on('click', function(e) {
e.preventDefault();
const productId = $(this).data('id');
const isPlus = $(this).hasClass('plus');
const $qtyValue = $(this).siblings('.products__qty-value');
let quantity = parseInt($qtyValue.text());
if (isPlus) { quantity++; }
else if (quantity > 1) { quantity--; }
else { return; }
$qtyValue.text(quantity);
$.ajax({
url: '/cite_practica/cart/update',
method: 'POST',
data: { product_id: productId, quantity: quantity },
dataType: 'json',
success: function(result) {
if (result.success) {
updateTotals();
$('.cart-count').text(result.cart_count);
}
}
});
});
// Удаление товара
$('.remove-from-cart').on('click', function(e) {
e.preventDefault();
const productId = $(this).data('id');
const $item = $(this).closest('.products__item');
if (!confirm('Удалить товар из корзины?')) return;
$.ajax({
url: '/cite_practica/cart/remove',
method: 'POST',
data: { product_id: productId },
dataType: 'json',
success: function(result) {
if (result.success) {
$item.fadeOut(300, function() {
$(this).remove();
updateTotals();
$('.cart-count').text(result.cart_count);
if ($('#cartItems .products__item').length === 0) {
location.reload();
}
});
}
}
});
});
function updateTotals() {
let productsTotal = 0;
let totalCount = 0;
$('.products__item').each(function() {
const price = parseInt($(this).data('price'));
const quantity = parseInt($(this).find('.products__qty-value').text());
productsTotal += price * quantity;
totalCount += quantity;
});
const delivery = parseFloat($('input[name="delivery_price"]').val());
const discount = parseFloat($('input[name="discount"]').val());
const finalTotal = productsTotal + delivery - discount;
$('.products-total').text(productsTotal.toLocaleString('ru-RU') + ' ₽');
$('.summary-count').text(totalCount);
$('.final-total').text(finalTotal.toLocaleString('ru-RU') + ' ₽');
}
// Промокод
$('#applyPromo').click(function() {
const promoCode = $('#promo_code').val().toUpperCase();
if (promoCode === 'SALE10') {
const productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, ''));
const discount = Math.round(productsTotal * 0.1);
$('input[name="discount"]').val(discount);
$('.discount-total').text(discount.toLocaleString('ru-RU') + ' ₽');
showNotification('Промокод применен! Скидка 10%');
updateTotals();
} else if (promoCode === 'FREE') {
$('input[name="delivery_price"]').val(0);
$('.delivery-price').text('0 ₽');
showNotification('Промокод применен! Бесплатная доставка');
updateTotals();
} else if (promoCode) {
showNotification('Промокод недействителен', 'error');
}
});
// Оформление заказа
$('#orderForm').submit(function(e) {
e.preventDefault();
if (!$('#privacy-checkbox').is(':checked')) {
showNotification('Необходимо согласие на обработку данных', 'error');
return;
}
$('#submit-order').prop('disabled', true).text('ОБРАБОТКА...');
$.ajax({
url: '/cite_practica/order',
method: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(result) {
if (result.success) {
showNotification('Заказ успешно оформлен!');
setTimeout(function() {
window.location.href = '/cite_practica/';
}, 1500);
} else {
showNotification('Ошибка: ' + result.message, 'error');
$('#submit-order').prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
}
},
error: function() {
showNotification('Ошибка сервера', 'error');
$('#submit-order').prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
}
});
});
});
</script>

18
app/Views/errors/404.php Normal file
View File

@@ -0,0 +1,18 @@
<?php $title = 'Страница не найдена'; ?>
<div style="text-align: center; padding: 80px 20px;">
<h1 style="font-size: 120px; color: #453227; margin: 0;">404</h1>
<h2 style="color: #617365; margin: 20px 0;">Страница не найдена</h2>
<p style="color: #666; margin: 20px 0; max-width: 500px; margin-left: auto; margin-right: auto;">
К сожалению, запрошенная страница не существует или была перемещена.
</p>
<div style="margin-top: 30px;">
<a href="/cite_practica/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
<i class="fas fa-home"></i> На главную
</a>
<a href="/cite_practica/catalog" class="btn" style="display: inline-block; background: #617365; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none; margin-left: 10px;">
<i class="fas fa-store"></i> В каталог
</a>
</div>
</div>

15
app/Views/errors/500.php Normal file
View File

@@ -0,0 +1,15 @@
<?php $title = 'Ошибка сервера'; ?>
<div style="text-align: center; padding: 80px 20px;">
<h1 style="font-size: 120px; color: #dc3545; margin: 0;">500</h1>
<h2 style="color: #453227; margin: 20px 0;">Внутренняя ошибка сервера</h2>
<p style="color: #666; margin: 20px 0; max-width: 500px; margin-left: auto; margin-right: auto;">
Произошла ошибка при обработке вашего запроса. Мы уже работаем над её устранением.
</p>
<div style="margin-top: 30px;">
<a href="/cite_practica/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
<i class="fas fa-home"></i> На главную
</a>
</div>
</div>

153
app/Views/home/index.php Normal file
View File

@@ -0,0 +1,153 @@
<?php $title = 'Главная'; ?>
<section class="hero">
<div class="container hero__content">
<div class="hero__image-block">
<div class="hero__circle"></div>
<img src="/cite_practica/img/chair.PNG" alt="Кресло и торшер" class="hero__img">
</div>
<div class="hero__text-block">
<h1>ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР</h1>
<p class="hero__usp-text">Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.</p>
<?php if ($isLoggedIn): ?>
<a href="/cite_practica/catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php else: ?>
<a href="/cite_practica/login" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php endif; ?>
</div>
</div>
</section>
<section class="advantages">
<div class="container">
<div class="advantages__header">
<h2>ПОЧЕМУ <br>ВЫБИРАЮТ НАС?</h2>
<div class="advantages__items">
<div class="advantage-item">
<span class="advantage-item__number">1</span>
<h4>ГАРАНТИЯ ВЫСОЧАЙШЕГО КАЧЕСТВА</h4>
<p>Собственное производство и строгий контроль на всех этапах.</p>
</div>
<div class="advantage-item">
<span class="advantage-item__number">2</span>
<h4>ИСПОЛЬЗОВАНИЕ НАДЕЖНЫХ МАТЕРИАЛОВ</h4>
<p>Гарантия безопасности и долговечности.</p>
</div>
<div class="advantage-item">
<span class="advantage-item__number">3</span>
<h4>ИНДИВИДУАЛЬНЫЙ ПОДХОД И ГИБКОСТЬ УСЛОВИЙ</h4>
<p>Реализуем проекты любой сложности по вашим техническим заданиям.</p>
</div>
</div>
</div>
<div class="promo-images">
<div class="promo-image-col">
<img src="/cite_practica/img/спальня.jpg" alt="Кровать и тумба">
<div class="image-overlay-text">
<h4>НОВИНКИ В КАТЕГОРИЯХ <br>МЯГКАЯ МЕБЕЛЬ</h4>
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a>
</div>
</div>
<div class="promo-image-col">
<img src="/cite_practica/img/диван.jpg" alt="Диван в гостиной">
<div class="image-overlay-text">
<h4>РАСПРОДАЖА <br>ПРЕДМЕТЫ ДЕКОРА</h4>
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a>
</div>
</div>
</div>
</div>
</section>
<section class="about">
<div class="container about__content">
<div class="about__column about__column--left">
<div class="about__text-block">
<h2>О НАС</h2>
<p class="text-justified">Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.</p>
</div>
<img src="/cite_practica/img/кресло_1.jpg" alt="Фиолетовое кресло" class="about__img about__img--small">
</div>
<div class="about__column about__column--right">
<img src="/cite_practica/img/диван_1.jpg" alt="Белый диван с подушками" class="about__img about__img--large">
<p class="about__caption">Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.</p>
</div>
</div>
</section>
<section class="solutions">
<div class="container">
<div class="solutions-slider">
<div class="solutions-slider__slides">
<div class="solutions-slider__slide">
<img src="/cite_practica/img/слайдер_1.jpg" class="solution-img" alt="Готовое решение для гостиной">
<div class="solution-text-overlay">
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ ГОСТИНОЙ</h2><br>
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
</div>
<a href="/cite_practica/catalog" class="solution-image-link">Подробнее</a>
</div>
</div>
</div>
</div>
</section>
<section class="stats">
<div class="container">
<div class="stats__items">
<div class="stat-item">
<div class="stat-number">10+</div>
<div class="stat-label">Лет работы</div>
</div>
<div class="stat-item">
<div class="stat-number">30 000+</div>
<div class="stat-label">Довольных покупателей</div>
</div>
<div class="stat-item">
<div class="stat-number">4500+</div>
<div class="stat-label">Реализованных заказов</div>
</div>
</div>
</div>
</section>
<section class="faq" id="faq">
<div class="container">
<h2>ОТВЕТЫ НА ВОПРОСЫ</h2>
<div class="faq__items">
<div class="faq-item">
<span class="number-circle">1</span>
<div class="faq-item__content">
<h4>Сколько времени занимает доставка?</h4>
<p>Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">2</span>
<div class="faq-item__content">
<h4>Нужно ли вносить предоплату?</h4>
<p>Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">3</span>
<div class="faq-item__content">
<h4>Предоставляется ли рассрочка или кредит?</h4>
<p>Да, мы предлагаем рассрочку на 6 или 12 месяцев без первоначального взноса.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">4</span>
<div class="faq-item__content">
<h4>Что делать, если мебель пришла с дефектом?</h4>
<p>Сообщите нам в течение 7 дней, мы решим вопрос о бесплатной замене или ремонте.</p>
</div>
</div>
</div>
<button class="btn primary-btn">Задать вопрос</button>
</div>
</section>

View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETERNA - Админ-панель</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f5f5f5; }
.admin-header { background: #453227; color: white; padding: 20px; display: flex; justify-content: space-between; align-items: center; }
.admin-header h1 { font-size: 20px; }
.admin-tabs { background: white; padding: 10px; border-bottom: 2px solid #453227; display: flex; gap: 10px; flex-wrap: wrap; }
.admin-tab { padding: 10px 20px; border-radius: 5px; text-decoration: none; color: #333; transition: all 0.3s; }
.admin-tab:hover, .admin-tab.active { background: #453227; color: white; }
.admin-content { padding: 20px; }
.form-container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); max-width: 800px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-control { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; transition: all 0.3s; }
.btn-primary { background: #453227; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-warning { background: #ffc107; color: #333; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn:hover { opacity: 0.9; }
.alert { padding: 15px; border-radius: 4px; margin-bottom: 20px; }
.alert-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
table { width: 100%; border-collapse: collapse; background: white; }
th, td { padding: 10px; border: 1px solid #ddd; text-align: left; }
th { background: #f8f9fa; }
.action-buttons { display: flex; gap: 5px; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
.stat-card { background: white; padding: 20px; border-radius: 5px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.stat-card h3 { font-size: 32px; color: #453227; margin-bottom: 5px; }
.stat-card p { color: #666; }
</style>
</head>
<body>
<div class="admin-header">
<h1><i class="fas fa-user-shield"></i> Админ-панель AETERNA</h1>
<div>
<span><?= htmlspecialchars($user['email'] ?? 'Администратор') ?></span>
<a href="/cite_practica/catalog" class="btn btn-primary" style="margin-left: 10px;">В каталог</a>
<a href="/cite_practica/logout" class="btn btn-danger" style="margin-left: 10px;">Выйти</a>
</div>
</div>
<div class="admin-tabs">
<a href="/cite_practica/admin" class="admin-tab <?= ($action ?? '') === 'dashboard' ? 'active' : '' ?>">
<i class="fas fa-tachometer-alt"></i> Дашборд
</a>
<a href="/cite_practica/admin/products" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/products') ? 'active' : '' ?>">
<i class="fas fa-box"></i> Товары
</a>
<a href="/cite_practica/admin/categories" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/categories') ? 'active' : '' ?>">
<i class="fas fa-tags"></i> Категории
</a>
<a href="/cite_practica/admin/orders" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/orders') ? 'active' : '' ?>">
<i class="fas fa-shopping-cart"></i> Заказы
</a>
<a href="/cite_practica/admin/users" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/users') ? 'active' : '' ?>">
<i class="fas fa-users"></i> Пользователи
</a>
</div>
<div class="admin-content">
<?= $content ?>
</div>
</body>
</html>

View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETERNA - <?= $title ?? 'Мебель и Интерьер' ?></title>
<link rel="stylesheet/less" type="text/css" href="/cite_practica/style_for_cite.less">
<script src="https://cdn.jsdelivr.net/npm/less"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
.user-profile-dropdown { position: relative; display: inline-block; }
.user-profile-toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; padding: 8px 12px; border-radius: 4px; transition: all 0.3s ease; }
.user-profile-toggle:hover { background-color: rgba(0, 0, 0, 0.05); }
.user-avatar { width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #617365 0%, #453227 100%); color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 16px; }
.user-info { display: flex; flex-direction: column; }
.user-email { font-size: 12px; color: #666; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-status { font-size: 10px; padding: 2px 8px; border-radius: 12px; text-transform: uppercase; font-weight: bold; text-align: center; margin-top: 2px; }
.user-status.admin { background-color: #617365; color: white; }
.user-status.user { background-color: #28a745; color: white; }
.user-profile-menu { display: none; position: absolute; top: 100%; right: 0; background: white; min-width: 280px; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.15); z-index: 1000; padding-top: 10px; border: 1px solid #e0e0e0; }
.user-profile-dropdown:hover .user-profile-menu { display: block; }
.user-profile-header { padding: 15px; background: #f8f9fa; border-bottom: 1px solid #e0e0e0; }
.user-profile-name { font-weight: bold; margin-bottom: 5px; color: #333; display: flex; align-items: center; gap: 8px; }
.user-profile-details { font-size: 12px; color: #666; }
.user-profile-links { list-style: none; padding: 10px 0; margin: 0; }
.user-profile-links a { display: flex; align-items: center; gap: 10px; padding: 10px 15px; color: #333; text-decoration: none; transition: all 0.3s ease; border-left: 3px solid transparent; }
.user-profile-links a:hover { background-color: #f5f5f5; border-left-color: #453227; color: #453227; }
.logout-link { color: #dc3545 !important; }
.logout-link:hover { background-color: #ffe6e6 !important; border-left-color: #dc3545 !important; }
.cart-icon { position: relative; margin-right: 15px; }
.cart-count { position: absolute; top: -8px; right: -8px; background: #dc3545; color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 11px; display: flex; align-items: center; justify-content: center; }
.notification { position: fixed; top: 20px; right: 20px; padding: 15px 20px; background: #28a745; color: white; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; transform: translateX(150%); transition: transform 0.3s ease; max-width: 300px; }
.notification.show { transform: translateX(0); }
.notification.error { background: #dc3545; }
</style>
</head>
<body>
<div id="notification" class="notification"></div>
<?= \App\Core\View::partial('header', ['user' => $user ?? null, 'isLoggedIn' => $isLoggedIn ?? false, 'isAdmin' => $isAdmin ?? false]) ?>
<main>
<?= $content ?>
</main>
<?= \App\Core\View::partial('footer') ?>
<script>
function showNotification(message, type = 'success') {
const notification = $('#notification');
notification.text(message);
notification.removeClass('success error').addClass(type + ' show');
setTimeout(function() { notification.removeClass('show'); }, 3000);
}
$(document).ready(function() {
// Обновляем счетчик корзины
$.get('/cite_practica/cart/count', function(response) {
if (response.success) {
$('.cart-count').text(response.cart_count);
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<?php $title = 'Доставка и оплата'; ?>
<main class="delivery-page">
<div class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Доставка и оплата</span>
</div>
<h1 style="color: #453227; margin: 30px 0;">Доставка и оплата</h1>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 40px;">
<div>
<h2 style="color: #453227; margin-bottom: 20px;"><i class="fas fa-truck"></i> Способы доставки</h2>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 15px;">
<h4 style="color: #453227;">Курьерская доставка</h4>
<p style="color: #666; margin-top: 10px;">Доставка до квартиры/офиса в удобное для вас время</p>
<ul style="color: #666; margin-top: 10px; padding-left: 20px;">
<li>По Москве: 1-3 дня</li>
<li>Московская область: 2-5 дней</li>
<li>Регионы России: 5-14 дней</li>
</ul>
<p style="color: #617365; font-weight: bold; margin-top: 10px;">от 2 000 ₽</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 15px;">
<h4 style="color: #453227;">Самовывоз</h4>
<p style="color: #666; margin-top: 10px;">Забрать заказ можно с нашего склада</p>
<p style="color: #666; margin-top: 5px;">Адрес: г. Москва, ул. Примерная, д. 1</p>
<p style="color: #617365; font-weight: bold; margin-top: 10px;">Бесплатно</p>
</div>
<div style="background: #d4edda; padding: 15px; border-radius: 8px;">
<p style="color: #155724;"><i class="fas fa-gift"></i> <strong>Бесплатная доставка</strong> при заказе от 50 000 ₽</p>
</div>
</div>
<div>
<h2 style="color: #453227; margin-bottom: 20px;"><i class="fas fa-credit-card"></i> Способы оплаты</h2>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 15px;">
<h4 style="color: #453227;"><i class="far fa-credit-card"></i> Банковская карта</h4>
<p style="color: #666; margin-top: 10px;">Visa, Mastercard, МИР - онлайн или при получении</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 15px;">
<h4 style="color: #453227;"><i class="fas fa-money-bill-wave"></i> Наличные</h4>
<p style="color: #666; margin-top: 10px;">Оплата курьеру при получении заказа</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 15px;">
<h4 style="color: #453227;"><i class="fas fa-file-invoice"></i> Безналичный расчет</h4>
<p style="color: #666; margin-top: 10px;">Для юридических лиц с выставлением счета</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px;">
<h4 style="color: #453227;"><i class="fas fa-clock"></i> Рассрочка</h4>
<p style="color: #666; margin-top: 10px;">Рассрочка на 6 или 12 месяцев без переплаты</p>
</div>
</div>
</div>
<div style="background: #fff3cd; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h4 style="color: #856404;"><i class="fas fa-info-circle"></i> Важная информация</h4>
<ul style="color: #856404; margin-top: 10px; padding-left: 20px;">
<li>При получении товара проверьте комплектность и целостность упаковки</li>
<li>В случае обнаружения повреждений составьте акт с курьером</li>
<li>Сохраняйте упаковку до окончания гарантийного срока</li>
</ul>
</div>
</div>
</main>

View File

@@ -0,0 +1,68 @@
<?php $title = 'Услуги'; ?>
<main class="services-page">
<div class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Услуги</span>
</div>
<h1 style="color: #453227; margin: 30px 0;">Наши услуги</h1>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-bottom: 40px;">
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #453227;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-drafting-compass"></i> Дизайн-проект</h3>
<p style="color: #666; line-height: 1.6;">
Наши дизайнеры создадут уникальный проект интерьера с подбором мебели, которая идеально впишется в ваше пространство.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">от 15 000 ₽</p>
</div>
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #617365;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-ruler-combined"></i> Замеры</h3>
<p style="color: #666; line-height: 1.6;">
Бесплатный выезд замерщика для точного определения размеров и особенностей вашего помещения.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">Бесплатно</p>
</div>
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #453227;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-tools"></i> Сборка мебели</h3>
<p style="color: #666; line-height: 1.6;">
Профессиональная сборка мебели нашими специалистами с гарантией качества работ.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">от 3 000 ₽</p>
</div>
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #617365;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-truck"></i> Подъем на этаж</h3>
<p style="color: #666; line-height: 1.6;">
Услуга подъема мебели на любой этаж, включая помещения без лифта.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">от 500 ₽ за этаж</p>
</div>
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #453227;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-recycle"></i> Вывоз старой мебели</h3>
<p style="color: #666; line-height: 1.6;">
Демонтаж и вывоз старой мебели для освобождения пространства перед доставкой новой.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">от 2 000 ₽</p>
</div>
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px; border-left: 4px solid #617365;">
<h3 style="color: #453227; margin-bottom: 15px;"><i class="fas fa-paint-roller"></i> Реставрация</h3>
<p style="color: #666; line-height: 1.6;">
Восстановление и обновление мебели: перетяжка, покраска, замена фурнитуры.
</p>
<p style="color: #617365; font-weight: bold; margin-top: 15px;">от 5 000 ₽</p>
</div>
</div>
<div style="background: linear-gradient(135deg, #453227 0%, #617365 100%); color: white; padding: 40px; border-radius: 8px; text-align: center;">
<h2>Нужна консультация?</h2>
<p style="margin: 20px 0;">Позвоните нам или оставьте заявку, и мы свяжемся с вами в ближайшее время</p>
<p style="font-size: 24px; font-weight: bold;">+7 (912) 999-12-23</p>
</div>
</div>
</main>

View File

@@ -0,0 +1,76 @@
<?php $title = 'Гарантия'; ?>
<main class="warranty-page">
<div class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Гарантия и возврат</span>
</div>
<h1 style="color: #453227; margin: 30px 0;">Гарантия и возврат</h1>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 40px;">
<div>
<h2 style="color: #453227; margin-bottom: 20px;"><i class="fas fa-shield-alt"></i> Гарантийные обязательства</h2>
<div style="background: #f8f9fa; padding: 25px; border-radius: 8px; margin-bottom: 20px;">
<h4 style="color: #617365; font-size: 36px; margin-bottom: 10px;">24 месяца</h4>
<p style="color: #453227; font-weight: bold;">Гарантия на всю мебель</p>
<p style="color: #666; margin-top: 10px;">Мы уверены в качестве нашей продукции и предоставляем расширенную гарантию на все изделия</p>
</div>
<h3 style="color: #453227; margin-bottom: 15px;">Гарантия распространяется на:</h3>
<ul style="color: #666; line-height: 2; padding-left: 20px;">
<li>Производственные дефекты</li>
<li>Дефекты материалов</li>
<li>Неисправность механизмов</li>
<li>Отклонения в размерах</li>
</ul>
<h3 style="color: #453227; margin: 20px 0 15px;">Гарантия не распространяется на:</h3>
<ul style="color: #666; line-height: 2; padding-left: 20px;">
<li>Механические повреждения</li>
<li>Повреждения от влаги/огня</li>
<li>Неправильную эксплуатацию</li>
<li>Естественный износ</li>
</ul>
</div>
<div>
<h2 style="color: #453227; margin-bottom: 20px;"><i class="fas fa-undo"></i> Возврат и обмен</h2>
<div style="background: #d4edda; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
<p style="color: #155724;"><i class="fas fa-check-circle"></i> <strong>14 дней</strong> на возврат товара надлежащего качества</p>
</div>
<h3 style="color: #453227; margin-bottom: 15px;">Условия возврата:</h3>
<ul style="color: #666; line-height: 2; padding-left: 20px;">
<li>Товар не был в употреблении</li>
<li>Сохранены товарный вид и упаковка</li>
<li>Сохранены все ярлыки и бирки</li>
<li>Есть документ, подтверждающий покупку</li>
</ul>
<h3 style="color: #453227; margin: 20px 0 15px;">Как оформить возврат:</h3>
<ol style="color: #666; line-height: 2; padding-left: 20px;">
<li>Свяжитесь с нами по телефону или email</li>
<li>Опишите причину возврата</li>
<li>Получите номер заявки на возврат</li>
<li>Отправьте товар или дождитесь курьера</li>
<li>Получите деньги в течение 10 дней</li>
</ol>
<div style="background: #fff3cd; padding: 15px; border-radius: 8px; margin-top: 20px;">
<p style="color: #856404;"><i class="fas fa-exclamation-triangle"></i> Мебель, изготовленная по индивидуальному заказу, обмену и возврату не подлежит</p>
</div>
</div>
</div>
<div style="background: linear-gradient(135deg, #453227 0%, #617365 100%); color: white; padding: 40px; border-radius: 8px; text-align: center;">
<h2>Остались вопросы?</h2>
<p style="margin: 20px 0;">Свяжитесь с нашей службой поддержки</p>
<p style="font-size: 24px; font-weight: bold;">+7 (912) 999-12-23</p>
<p style="margin-top: 10px;">aeterna@mail.ru</p>
</div>
</div>
</main>

View File

@@ -0,0 +1,48 @@
<footer class="footer" id="footer">
<div class="container footer__content">
<div class="footer__col footer--logo">
<div class="logo">AETERNA</div>
</div>
<div class="footer__col">
<h5>ПОКУПАТЕЛЮ</h5>
<ul>
<li><a href="/cite_practica/catalog">Каталог</a></li>
<li><a href="/cite_practica/services">Услуги</a></li>
</ul>
</div>
<div class="footer__col">
<h5>ПОМОЩЬ</h5>
<ul>
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li>
<li><a href="/cite_practica/warranty">Гарантия и возврат</a></li>
<li><a href="/cite_practica/#faq">Ответы на вопросы</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</div>
<div class="footer__col">
<h5>КОНТАКТЫ</h5>
<p>aeterna@mail.ru</p>
<p>+7(912)999-12-23</p>
<div class="social-icons">
<span class="icon"><i class="fab fa-telegram"></i></span>
<span class="icon"><i class="fab fa-instagram"></i></span>
<span class="icon"><i class="fab fa-vk"></i></span>
</div>
</div>
<div class="footer__col">
<h5>ПРИНИМАЕМ К ОПЛАТЕ</h5>
<div class="payment-icons">
<span class="pay-icon"><i class="fab fa-cc-visa"></i></span>
<span class="pay-icon"><i class="fab fa-cc-mastercard"></i></span>
</div>
</div>
</div>
<div class="copyright">
<p>© 2025 AETERNA. Все права защищены.</p>
</div>
</footer>

View File

@@ -0,0 +1,109 @@
<?php
$isLoggedIn = $isLoggedIn ?? \App\Core\View::isAuthenticated();
$isAdmin = $isAdmin ?? \App\Core\View::isAdmin();
$user = $user ?? \App\Core\View::currentUser();
?>
<header class="header">
<div class="header__top">
<div class="container header__top-content">
<a href="/cite_practica/" class="logo">AETERNA</a>
<div class="search-catalog">
<div class="catalog-dropdown">
Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu">
<ul>
<li><a href="/cite_practica/catalog">Все товары</a></li>
<li><a href="/cite_practica/catalog?category=1">Диваны</a></li>
<li><a href="/cite_practica/catalog?category=2">Кровати</a></li>
<li><a href="/cite_practica/catalog?category=3">Шкафы</a></li>
<li><a href="/cite_practica/catalog?category=4">Стулья</a></li>
<li><a href="/cite_practica/catalog?category=5">Столы</a></li>
</ul>
</div>
</div>
<div class="search-box">
<form method="GET" action="/cite_practica/catalog" style="display: flex; width: 100%;">
<input type="text" name="search" placeholder="Поиск товаров" style="border: none; width: 100%; padding: 10px;">
<button type="submit" style="background: none; border: none; cursor: pointer;">
<span class="search-icon"><i class="fas fa-search"></i></span>
</button>
</form>
</div>
</div>
<div class="header__icons--top">
<?php if ($isLoggedIn): ?>
<a href="/cite_practica/cart" class="icon cart-icon">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
</a>
<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="/cite_practica/cart"><i class="fas fa-shopping-bag"></i> Корзина</a></li>
<?php if ($isAdmin): ?>
<li><a href="/cite_practica/admin"><i class="fas fa-user-shield"></i> Админ-панель</a></li>
<?php endif; ?>
<li><a href="/cite_practica/logout" class="logout-link"><i class="fas fa-sign-out-alt"></i> Выйти</a></li>
</ul>
</div>
</div>
<?php else: ?>
<a href="/cite_practica/login" class="icon"><i class="far fa-user"></i></a>
<a href="/cite_practica/login" style="font-size: 12px; color: #666; margin-left: 5px;">Войти</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="header__bottom">
<div class="container header__bottom-content">
<div class="catalog-menu">
<a href="/cite_practica/catalog" class="catalog-link">
<span class="catalog-lines">☰</span>
Каталог
</a>
</div>
<nav class="nav">
<ul class="nav-list">
<li><a href="/cite_practica/">Главная</a></li>
<li><a href="/cite_practica/services">Услуги</a></li>
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li>
<li><a href="/cite_practica/warranty">Гарантия</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</nav>
<div class="header-phone">+7(912)999-12-23</div>
</div>
</div>
</header>

View File

@@ -0,0 +1,195 @@
<?php
$title = 'Каталог';
use App\Core\View;
?>
<style>
.catalog-wrapper { display: flex; gap: 30px; margin-top: 20px; }
.catalog-sidebar { width: 250px; flex-shrink: 0; }
.catalog-products { flex: 1; }
.filter-group { margin-bottom: 25px; }
.filter-title { color: #453227; font-size: 16px; font-weight: bold; margin-bottom: 15px; border-bottom: 2px solid #453227; padding-bottom: 5px; }
.filter-list { list-style: none; padding: 0; margin: 0; }
.filter-list li { margin-bottom: 8px; }
.filter-list a { color: #453227; text-decoration: none; font-size: 14px; transition: all 0.3s ease; }
.filter-list a:hover { color: #617365; padding-left: 5px; }
.active-category { font-weight: bold !important; color: #617365 !important; }
.products-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
.product-card { background: #f5f5f5; border-radius: 8px; overflow: hidden; position: relative; cursor: pointer; transition: transform 0.3s; }
.product-card:hover { transform: translateY(-5px); }
.product-card img { width: 100%; height: 200px; object-fit: cover; }
.product-info { padding: 15px; }
.product-info .name { font-weight: bold; color: #453227; margin-bottom: 5px; }
.product-info .price { color: #617365; font-size: 18px; font-weight: bold; }
.add-to-cart-btn { position: absolute; bottom: 15px; right: 15px; background: rgba(255,255,255,0.9); width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #453227; border: 1px solid #eee; transition: all 0.3s; }
.add-to-cart-btn:hover { background: #453227; color: white; }
.admin-panel { background: #f8f9fa; padding: 15px; margin-bottom: 20px; border-radius: 8px; border-left: 4px solid #617365; }
.admin-btn { background: #617365; color: white; padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin: 0 5px 5px 0; text-decoration: none; display: inline-block; }
.admin-btn:hover { background: #453227; color: white; }
.user-welcome { background: #f5e9dc; color: #453227; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #453227; }
.product-card.unavailable { opacity: 0.6; filter: grayscale(0.7); }
</style>
<main class="catalog-main">
<div class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Каталог</span>
</div>
<?php if (!empty($success)): ?>
<div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; margin: 20px 0;">
<?= htmlspecialchars($success) ?>
</div>
<?php endif; ?>
<?php if ($isAdmin): ?>
<div class="admin-panel">
<h3 style="margin-bottom: 15px; color: #453227;">
<i class="fas fa-user-shield"></i> Панель управления каталогом
</h3>
<div>
<a href="/cite_practica/admin/products" class="admin-btn"><i class="fas fa-boxes"></i> Управление каталогом</a>
<a href="/cite_practica/admin/products/add" class="admin-btn"><i class="fas fa-plus"></i> Добавить товар</a>
<a href="/cite_practica/admin/categories" class="admin-btn"><i class="fas fa-tags"></i> Категории</a>
<a href="/cite_practica/admin/orders" class="admin-btn"><i class="fas fa-shopping-cart"></i> Заказы</a>
</div>
</div>
<?php endif; ?>
<div class="user-welcome">
<i class="fas fa-user-check"></i> Добро пожаловать, <strong><?= htmlspecialchars($user['full_name'] ?? $user['email']) ?></strong>!
<?php if ($isAdmin): ?>
<span style="background: #453227; color: white; padding: 3px 8px; border-radius: 4px; font-size: 12px; margin-left: 10px;">
<i class="fas fa-user-shield"></i> Администратор
</span>
<?php endif; ?>
</div>
<div class="catalog-wrapper">
<aside class="catalog-sidebar">
<form method="GET" action="/cite_practica/catalog" id="filterForm">
<div class="filter-group">
<h4 class="filter-title">КАТЕГОРИИ</h4>
<ul class="filter-list">
<li><a href="/cite_practica/catalog" class="<?= empty($filters['category_id']) ? 'active-category' : '' ?>">Все товары</a></li>
<?php foreach ($categories as $category): ?>
<li>
<a href="/cite_practica/catalog?category=<?= $category['category_id'] ?>"
class="<?= $filters['category_id'] == $category['category_id'] ? 'active-category' : '' ?>">
<?= htmlspecialchars($category['name']) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="filter-group">
<h4 class="filter-title">СТОИМОСТЬ</h4>
<div class="price-range">
<input type="range" min="0" max="100000" value="<?= $filters['max_price'] ?>"
step="1000" id="priceSlider" name="max_price" style="width: 100%;">
<div id="priceDisplay" style="text-align: center; margin-top: 10px; font-weight: bold; color: #453227;">
До <?= number_format($filters['max_price'], 0, '', ' ') ?> ₽
</div>
</div>
</div>
<?php if (!empty($availableColors)): ?>
<div class="filter-group">
<h4 class="filter-title">ЦВЕТ</h4>
<ul class="filter-list">
<?php foreach ($availableColors as $color): ?>
<li>
<label>
<input type="checkbox" name="colors[]" value="<?= htmlspecialchars($color) ?>"
<?= in_array($color, $filters['colors']) ? 'checked' : '' ?>>
<?= htmlspecialchars($color) ?>
</label>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<button type="submit" style="width: 100%; background: #453227; color: white; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
ПРИМЕНИТЬ ФИЛЬТРЫ
</button>
</form>
</aside>
<section class="catalog-products">
<div style="margin-bottom: 20px;">
<h2 style="color: #453227; margin-bottom: 10px;">
Каталог мебели
<span style="font-size: 14px; color: #666; font-weight: normal;">
(<?= count($products) ?> товаров)
</span>
</h2>
<?php if (!empty($filters['search'])): ?>
<p style="color: #666;">
Результаты поиска: "<strong><?= htmlspecialchars($filters['search']) ?></strong>"
<a href="/cite_practica/catalog" style="margin-left: 10px; color: #617365;">
<i class="fas fa-times"></i> Очистить
</a>
</p>
<?php endif; ?>
</div>
<div class="products-container">
<?php if (empty($products)): ?>
<p style="grid-column: 1/-1; text-align: center; padding: 40px; color: #666;">
Товары не найдены
</p>
<?php else: ?>
<?php foreach ($products as $product): ?>
<div class="product-card <?= !$product['is_available'] ? 'unavailable' : '' ?>"
onclick="window.location.href='/cite_practica/product/<?= $product['product_id'] ?>'"
data-product-id="<?= $product['product_id'] ?>">
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
alt="<?= htmlspecialchars($product['name']) ?>">
<div class="product-info">
<div class="name"><?= htmlspecialchars($product['name']) ?></div>
<div class="price"><?= View::formatPrice($product['price']) ?></div>
</div>
<?php if ($product['is_available']): ?>
<i class="fas fa-shopping-cart add-to-cart-btn"
onclick="event.stopPropagation(); addToCart(<?= $product['product_id'] ?>, '<?= addslashes($product['name']) ?>')"
title="Добавить в корзину"></i>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</section>
</div>
</div>
</main>
<script>
$('#priceSlider').on('input', function() {
const value = $(this).val();
$('#priceDisplay').text('До ' + new Intl.NumberFormat('ru-RU').format(value) + ' ₽');
});
function addToCart(productId, productName) {
$.ajax({
url: '/cite_practica/cart/add',
method: 'POST',
data: { product_id: productId, quantity: 1 },
dataType: 'json',
success: function(result) {
if (result.success) {
showNotification('Товар "' + productName + '" добавлен в корзину!');
$('.cart-count').text(result.cart_count);
} else {
showNotification('Ошибка: ' + result.message, 'error');
}
},
error: function() {
showNotification('Ошибка сервера', 'error');
}
});
}
</script>

210
app/Views/products/show.php Normal file
View File

@@ -0,0 +1,210 @@
<?php
$title = $product['name'];
use App\Core\View;
?>
<style>
.product__section { display: grid; grid-template-columns: 350px 1fr; gap: 30px; margin: 30px 0; }
.product__main-image { width: 350px; height: 350px; background: #f8f9fa; border-radius: 8px; overflow: hidden; display: flex; align-items: center; justify-content: center; }
.product__main-image img { width: 100%; height: 100%; object-fit: contain; }
.product__info h1 { color: #453227; margin-bottom: 15px; }
.product__price { margin: 20px 0; }
.current-price { font-size: 28px; font-weight: bold; color: #453227; }
.old-price { font-size: 18px; color: #999; text-decoration: line-through; margin-left: 15px; }
.discount-badge { background: #dc3545; color: white; padding: 5px 10px; border-radius: 4px; font-size: 14px; margin-left: 10px; }
.stock-status { display: inline-block; padding: 8px 15px; border-radius: 4px; font-weight: bold; margin: 15px 0; }
.in-stock { background: #d4edda; color: #155724; }
.low-stock { background: #fff3cd; color: #856404; }
.out-of-stock { background: #f8d7da; color: #721c24; }
.product-attributes { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
.attribute-row { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #ddd; }
.attribute-label { font-weight: bold; color: #453227; }
.attribute-value { color: #617365; }
.product__purchase { display: flex; gap: 15px; align-items: center; margin: 20px 0; }
.product__quantity { display: flex; align-items: center; gap: 10px; }
.product__qty-btn { width: 35px; height: 35px; border: 1px solid #ddd; background: white; cursor: pointer; font-size: 18px; border-radius: 4px; }
.product__qty-value { width: 50px; text-align: center; border: 1px solid #ddd; padding: 8px; border-radius: 4px; }
.product__actions { display: flex; gap: 10px; }
.similar-products { margin: 40px 0; }
.similar-products h2 { color: #453227; margin-bottom: 20px; }
.products-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.product-card { background: #f5f5f5; border-radius: 8px; overflow: hidden; }
.product-card img { width: 100%; height: 200px; object-fit: cover; }
.product-card .product-info { padding: 15px; }
</style>
<main class="container">
<div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> •
<a href="/cite_practica/catalog">Каталог</a> •
<?php if ($product['category_name']): ?>
<a href="/cite_practica/catalog?category=<?= $product['category_id'] ?>">
<?= htmlspecialchars($product['category_name']) ?>
</a> •
<?php endif; ?>
<span><?= htmlspecialchars($product['name']) ?></span>
</div>
<div class="product__section">
<div class="product__gallery">
<div class="product__main-image">
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
alt="<?= htmlspecialchars($product['name']) ?>">
</div>
</div>
<div class="product__info">
<h1><?= htmlspecialchars($product['name']) ?></h1>
<div class="product__rating">
<div class="stars">
<?php
$rating = $product['rating'] ?? 0;
for ($i = 1; $i <= 5; $i++) {
echo $i <= $rating ? '<span class="star filled">★</span>' : '<span class="star">☆</span>';
}
?>
</div>
<span>(<?= $product['review_count'] ?? 0 ?> отзывов)</span>
</div>
<div class="product__price">
<span class="current-price"><?= View::formatPrice($product['price']) ?></span>
<?php if ($product['old_price'] && $product['old_price'] > $product['price']): ?>
<span class="old-price"><?= View::formatPrice($product['old_price']) ?></span>
<span class="discount-badge">
-<?= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
</span>
<?php endif; ?>
</div>
<div class="stock-status <?php
if ($product['stock_quantity'] > 10) echo 'in-stock';
elseif ($product['stock_quantity'] > 0) echo 'low-stock';
else echo 'out-of-stock';
?>">
<?php
if ($product['stock_quantity'] > 10) {
echo '<i class="fas fa-check-circle"></i> В наличии';
} elseif ($product['stock_quantity'] > 0) {
echo '<i class="fas fa-exclamation-circle"></i> Осталось мало: ' . $product['stock_quantity'] . ' шт.';
} else {
echo '<i class="fas fa-times-circle"></i> Нет в наличии';
}
?>
</div>
<div class="product-attributes">
<div class="attribute-row">
<span class="attribute-label">Артикул:</span>
<span class="attribute-value"><?= $product['sku'] ?? 'N/A' ?></span>
</div>
<div class="attribute-row">
<span class="attribute-label">Категория:</span>
<span class="attribute-value"><?= htmlspecialchars($product['category_name'] ?? 'Без категории') ?></span>
</div>
<div class="attribute-row">
<span class="attribute-label">На складе:</span>
<span class="attribute-value"><?= $product['stock_quantity'] ?> шт.</span>
</div>
</div>
<p class="product__description">
<?= nl2br(htmlspecialchars($product['description'] ?? 'Описание отсутствует')) ?>
</p>
<?php if ($product['stock_quantity'] > 0): ?>
<div class="product__purchase">
<div class="product__quantity">
<button class="product__qty-btn minus">-</button>
<input type="number" class="product__qty-value" value="1" min="1" max="<?= $product['stock_quantity'] ?>">
<button class="product__qty-btn plus">+</button>
</div>
<div class="product__actions">
<button class="btn primary-btn" onclick="addToCart(<?= $product['product_id'] ?>)">
<i class="fas fa-shopping-cart"></i> В корзину
</button>
<button class="btn secondary-btn" onclick="buyNow(<?= $product['product_id'] ?>)">
<i class="fas fa-bolt"></i> Купить сейчас
</button>
</div>
</div>
<?php endif; ?>
<?php if ($isAdmin): ?>
<div style="margin-top: 20px;">
<a href="/cite_practica/admin/products/edit/<?= $product['product_id'] ?>" class="btn" style="background: #ffc107; color: #333;">
<i class="fas fa-edit"></i> Редактировать
</a>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!empty($similarProducts)): ?>
<section class="similar-products">
<h2>Похожие товары</h2>
<div class="products-grid">
<?php foreach ($similarProducts as $similar): ?>
<div class="product-card" onclick="window.location.href='/cite_practica/product/<?= $similar['product_id'] ?>'" style="cursor: pointer;">
<img src="/cite_practica/<?= htmlspecialchars($similar['image_url'] ?? 'img/1.jpg') ?>"
alt="<?= htmlspecialchars($similar['name']) ?>">
<div class="product-info">
<h3 style="font-size: 16px; color: #453227;"><?= htmlspecialchars($similar['name']) ?></h3>
<p style="color: #617365; font-weight: bold;"><?= View::formatPrice($similar['price']) ?></p>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</main>
<script>
$(document).ready(function() {
$('.product__qty-btn.plus').click(function() {
const $input = $('.product__qty-value');
let value = parseInt($input.val());
let max = parseInt($input.attr('max'));
if (value < max) $input.val(value + 1);
});
$('.product__qty-btn.minus').click(function() {
const $input = $('.product__qty-value');
let value = parseInt($input.val());
if (value > 1) $input.val(value - 1);
});
});
function addToCart(productId) {
const quantity = $('.product__qty-value').val();
$.ajax({
url: '/cite_practica/cart/add',
method: 'POST',
data: { product_id: productId, quantity: quantity },
dataType: 'json',
success: function(result) {
if (result.success) {
showNotification('Товар добавлен в корзину!');
$('.cart-count').text(result.cart_count);
} else {
showNotification('Ошибка: ' + result.message, 'error');
}
}
});
}
function buyNow(productId) {
const quantity = $('.product__qty-value').val();
$.ajax({
url: '/cite_practica/cart/add',
method: 'POST',
data: { product_id: productId, quantity: quantity },
success: function() {
window.location.href = '/cite_practica/cart';
}
});
}
</script>