11 Commits
lab_3 ... lab_6

Author SHA1 Message Date
qkrlnt
858bd39322 отчет маршруты 2025-05-29 03:34:27 +04:00
qkrlnt
4defafe3e7 отчет6 2025-05-28 19:49:57 +04:00
qkrlnt
da97a99436 доп 2025-05-27 15:39:50 +04:00
qkrlnt
d44d30cb05 nav 2025-05-27 11:38:59 +04:00
qkrlnt
ebbfcd0e93 changes 2025-05-24 10:45:37 +04:00
qkrlnt
e005c61030 done without .docx 2025-05-24 10:42:15 +04:00
cbda4dee82 working on.... 2025-05-23 22:33:12 +02:00
038f60d61e lab_6_start 2025-05-23 21:47:29 +02:00
d1b2cea66c minimal 2025-05-23 17:43:10 +02:00
311491d554 some changes 2025-05-23 17:33:42 +02:00
5fa65ad591 lab_5 2025-05-22 15:18:29 +02:00
34 changed files with 2017 additions and 885 deletions

View File

@@ -1,178 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин: ЛК</title>
<link rel="stylesheet" href="css/style.css" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
</head>
<body>
<header">
<div class="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" class="me-3" style="width: 200px; height: auto;">
<a href="newSite.html" class="text-decoration-none text-dark m-1">
<h1 class="display-4 h3 mt-3"><b>Название магазина</b></h1>
</a>
</div>
<navbar>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown"
data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul class="dropdown-menu" aria-labelledby="navigationDropdown">
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Account.html">Личный кабинет<i class="bi bi-person-circle ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Basket.html">Корзина<i class="bi bi-cart4 ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Order.html">Заказы<i class="bi bi-receipt ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Favorites.html">Избранное<i class="bi bi-heart-fill ms-2"></i></a></li>
</ul>
</div>
</navbar>
</header>
<div class="container mt-5">
<div class="card text-center mx-auto" style="max-width: 400px; max-height: 400px;">
<img src="images/бананы.jpg" class="card-img-top" alt="Профиль"
style="width: 100%; height: 300px; object-fit: cover;">
<div class="card-body">
<h3 class="card-title">Имя Фамилия</h3>
<p class="card-text">Описание</p>
</div>
</div>
</div>
<footer class="container mt-5">
<div class="bg-light p-4">
<h5>Помощь:</h5>
<div class="d-flex flex-wrap">
<div class="d-flex align-items-center me-4 mb-3">
<i class="bi bi-telephone-fill me-2"></i>
<a href="#" class="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" class="me-2" style="width: 24px; height: 24px;">
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank"
class="text-decoration-none text-dark">vk.com</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" class="me-2" style="width: 24px; height: 24px;">
<a href="#" class="text-decoration-none text-dark">tg.me</a>
</div>
<div class="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" class="me-2" style="width: 24px; height: 24px;">
<a href="mailto:ozon-zon-zon@mail.joke"
class="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script type="module">
let profile = {
firstName: "Иван",
lastName: "Иванов",
image: "images/бананы.jpg"
};
function renderProfile() {
const card = document.querySelector('.container .card');
card.innerHTML = `
<img src="${profile.image || 'images/бананы.jpg'}" class="card-img-top" alt="Профиль" style="width: 100%; height: 300px; object-fit: cover;">
<div class="card-body">
<h3 class="card-title">${profile.firstName} ${profile.lastName}</h3>
<button class="btn btn-primary" id="editProfileBtn"><i class="bi bi-pencil"></i> Редактировать профиль</button>
</div>
`;
card.querySelector('#editProfileBtn').onclick = showEditModal;
}
function showEditModal() {
let modalDiv = document.getElementById('profileModal');
if (!modalDiv) {
modalDiv = document.createElement('div');
modalDiv.className = 'modal fade';
modalDiv.id = 'profileModal';
modalDiv.tabIndex = -1;
modalDiv.innerHTML = `
<div class="modal-dialog">
<div class="modal-content" id="profileModalContent"></div>
</div>
`;
document.body.appendChild(modalDiv);
}
const modalContent = modalDiv.querySelector('#profileModalContent');
modalContent.innerHTML = `
<div class="modal-header">
<h5 class="modal-title">Редактировать профиль</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="profileForm">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Имя</label>
<input type="text" class="form-control" name="firstName" value="${profile.firstName}" required pattern="^[А-Яа-яЁё]+$" title="Только русские буквы!">
</div>
<div class="mb-3">
<label class="form-label">Фамилия</label>
<input type="text" class="form-control" name="lastName" value="${profile.lastName}" required pattern="^[А-Яа-яЁё]+$" title="Только русские буквы!">
</div>
<div class="mb-3">
<label class="form-label">Фото профиля</label>
<input type="file" class="form-control" name="image" accept="image/*">
<img id="profilePreviewImage" src="${profile.image || ''}" style="max-width:100%;margin-top:10px;${profile.image ? '' : 'display:none;'}"/>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">Сохранить</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
</div>
</form>
`;
const modal = new bootstrap.Modal(modalDiv);
modal.show();
// Предпросмотр картинки
const fileInput = modalContent.querySelector('input[type="file"]');
document.getElementById('profileForm').onsubmit = function (e) {
e.preventDefault();
const formData = new FormData(e.target);
const firstName = formData.get('firstName');
const lastName = formData.get('lastName');
// Валидация кириллицы
if (!/^[А-Яа-яЁё]+$/.test(firstName) || !/^[А-Яа-яЁё]+$/.test(lastName)) {
alert('Имя и фамилия должны быть только на русском!');
return;
}
profile.firstName = firstName;
profile.lastName = lastName;
if (fileInput.files.length) {
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function (event) {
profile.image = event.target.result; // base64
modal.hide();
renderProfile();
};
reader.readAsDataURL(file);
} else {
profile.image = fileInput.src || "images/бананы.jpg";
modal.hide();
renderProfile();
}
};
}
renderProfile();
</script>
</body>
</html>

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин: Корзина</title>
<link rel="stylesheet" href="css/style.css"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
</head>
<body>
<header">
<div class="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" class="me-3" style="width: 200px; height: auto;">
<a href="newSite.html" class="text-decoration-none text-dark m-1"><h1 class="display-4 h3 mt-3"><b>Название магазина</b></h1></a>
</div>
<navbar>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul class="dropdown-menu" aria-labelledby="navigationDropdown">
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Account.html">Личный кабинет<i class="bi bi-person-circle ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Basket.html">Корзина<i class="bi bi-cart4 ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Order.html">Заказы<i class="bi bi-receipt ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Favorites.html">Избранное<i class="bi bi-heart-fill ms-2"></i></a></li>
</ul>
</div>
</navbar>
</header>
<main class="container d-flex justify-content-center align-items-center" style="min-height: 60vh;">
<div class="card p-4 shadow" style="min-width: 60vw;">
<h2 class="text-center">Корзина</h2>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex align-items-center">
<img src="images/glasses.jpg" alt="Очки" style="width: 100px; height: 100px;" class="me-3">
Очки <span class="ms-auto">349 руб.</span>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="images/chery.jpg" alt="Chery Tiggo" style="width: 100px; height: 100px;" class="me-3">
Chery Tiggo 7 Pro Max <span class="ms-auto">5 руб.</span>
</li>
<li class="list-group-item d-flex align-items-center">
<img src="images/vanadiy.jpg" alt="Ванадий" style="width: 100px; height: 100px;" class="me-3">
Ванадий <span class="ms-auto">2099 руб.</span>
</li>
</ul>
<div class="text-center mt-3">
<a href="https://xn----7sbon6aucai8a.xn--p1ai/wa-data/public/shop/products/98/27/2798/images/6060/6060.970.jpg" target="_blank" class="btn btn-success w-100">
Оплатить
</a>
</div>
</div>
</main>
<footer class="container mt-5">
<div class="bg-light p-4">
<h5>Помощь:</h5>
<div class="d-flex flex-wrap">
<div class="d-flex align-items-center me-4 mb-3">
<i class="bi bi-telephone-fill me-2"></i>
<a href="#" class="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" class="me-2" style="width: 24px; height: 24px;">
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank" class="text-decoration-none text-dark">vk.com</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" class="me-2" style="width: 24px; height: 24px;">
<a href="#" class="text-decoration-none text-dark">tg.me</a>
</div>
<div class="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" class="me-2" style="width: 24px; height: 24px;">
<a href="mailto:ozon-zon-zon@mail.joke" class="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,79 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин: Избранное</title>
<link rel="stylesheet" href="css/style.css"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
</head>
<body>
<header">
<div class="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" class="me-3" style="width: 200px; height: auto;">
<a href="newSite.html" class="text-decoration-none text-dark m-1"><h1 class="display-4 h3 mt-3"><b>Название магазина</b></h1></a>
</div>
<navbar>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul class="dropdown-menu" aria-labelledby="navigationDropdown">
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Account.html">Личный кабинет<i class="bi bi-person-circle ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Basket.html">Корзина<i class="bi bi-cart4 ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Order.html">Заказы<i class="bi bi-receipt ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Favorites.html">Избранное<i class="bi bi-heart-fill ms-2"></i></a></li>
</ul>
</div>
</navbar>
</header>
<div class="container mt-4">
<h2 class="mb-4">Избранное</h2>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col">
<div class="card mx-auto" style="width: 70%;">
<img src="images/masha.jpg" class="card-img-top" alt="Женщина" style="width: 100%; height: 300px; object-fit: cover;">
<div class="card-body text-center">
<h5 class="card-title">Женщина</h5>
<p class="card-text">бесценна</p>
</div>
</div>
</div>
<div class="col">
<div class="card mx-auto" style="width: 70%;">
<img src="images/screwdriver.jpg" class="card-img-top" alt="Отвертка" style="width: 100%; height: 300px; object-fit: cover;">
<div class="card-body text-center">
<h5 class="card-title">Отвертка</h5>
<p class="card-text">219 руб</p>
</div>
</div>
</div>
</div>
</div>
<footer class="container mt-5">
<div class="bg-light p-4">
<h5>Помощь:</h5>
<div class="d-flex flex-wrap">
<div class="d-flex align-items-center me-4 mb-3">
<i class="bi bi-telephone-fill me-2"></i>
<a href="#" class="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" class="me-2" style="width: 24px; height: 24px;">
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank" class="text-decoration-none text-dark">vk.com</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" class="me-2" style="width: 24px; height: 24px;">
<a href="#" class="text-decoration-none text-dark">tg.me</a>
</div>
<div class="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" class="me-2" style="width: 24px; height: 24px;">
<a href="mailto:ozon-zon-zon@mail.joke" class="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,101 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин: Заказы</title>
<link rel="stylesheet" href="css/style.css"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
</head>
<body>
<header">
<div class="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" class="me-3" style="width: 200px; height: auto;">
<a href="newSite.html" class="text-decoration-none text-dark m-1"><h1 class="display-4 h3 mt-3"><b>Название магазина</b></h1></a>
</div>
<navbar>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul class="dropdown-menu" aria-labelledby="navigationDropdown">
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Account.html">Личный кабинет<i class="bi bi-person-circle ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Basket.html">Корзина<i class="bi bi-cart4 ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Order.html">Заказы<i class="bi bi-receipt ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center" href="Favorites.html">Избранное<i class="bi bi-heart-fill ms-2"></i></a></li>
</ul>
</div>
</navbar>
</header>
<div class="container mt-4">
<h1 class="text-center">Заказы</h1>
<div class="row">
<!-- Заказы в процессе -->
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header bg-warning text-dark">
<h2 class="h5 m-0">В процессе</h2>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex align-items-center mb-2">
<img src="images/spoon.jpg" class="me-2" style="min-width: 90px;"> Ложка
</li>
<li class="list-group-item d-flex align-items-center mb-2">
<img src="images/fork.jpg" class="me-2" style="min-width: 90px;"> Вилка
</li>
<li class="list-group-item d-flex align-items-center">
<img src="images/knife.jpg" class="me-2" style="min-width: 90px;"> Нож
</li>
</ul>
</div>
</div>
</div>
<!-- Завершённые заказы -->
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h2 class="h5 m-0">Завершённые</h2>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex align-items-center mb-2">
<img src="images/iron.jpg" class="me-2" style="min-width: 90px;"> Утюг
</li>
<li class="list-group-item d-flex align-items-center mb-2">
<img src="images/bananas.jpg" class="me-2" style="min-width: 90px;"> Бананы
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<footer class="container mt-5">
<div class="bg-light p-4">
<h5>Помощь:</h5>
<div class="d-flex flex-wrap">
<div class="d-flex align-items-center me-4 mb-3">
<i class="bi bi-telephone-fill me-2"></i>
<a href="#" class="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" class="me-2" style="width: 24px; height: 24px;">
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank" class="text-decoration-none text-dark">vk.com</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" class="me-2" style="width: 24px; height: 24px;">
<a href="#" class="text-decoration-none text-dark">tg.me</a>
</div>
<div class="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" class="me-2" style="width: 24px; height: 24px;">
<a href="mailto:ozon-zon-zon@mail.joke" class="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,90 +0,0 @@
/* Общие стили */
body {
background-color: #edf7f6;
}
/* Заголовки */
h2 {
text-align: center;
}
/* Изображения */
img {
width: 180px;
height: 150px;
margin-right: 10px;
object-fit: cover;
}
/* Ссылки */
a {
text-decoration: none;
color: black;
font-weight: bold;
}
/* Навигация */
navbar {
position: fixed;
top: 40px;
right: 20px;
z-index: 3;
}
/* Контент */
.goods img {
width: 250px;
height: 250px;
}
/* Заказы */
.inProcess h2 {
background-color: yellow;
}
.done h2 {
background-color: green;
}
/* Кнопка оплаты */
.buy {
position: fixed;
bottom: 20px;
right: 20px;
background-color: greenyellow;
text-align: center;
border: 2px solid black;
border-radius: 10px;
width: 110px;
height: 35px;
transition: background-color 0.3s ease-in-out;
}
.buy:hover {
background-color: lightgreen;
}
/* Личный кабинет */
.account img {
border: 3px solid black;
}
.recomended .card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.recomended .card:hover {
transform: translateY(-5px) scale(1.02);
}
/* Адаптивность */
@media only screen and (min-width: 400px) {
body {
font-size: 14px;
}
.buy {
width: 200px;
height: 60px;
}
}

82
db.json

File diff suppressed because one or more lines are too long

15
index.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>

View File

@@ -1,52 +0,0 @@
// js/controller.js
import Model from './model.js';
import View from './view.js';
export default {
async init() {
this.products = await Model.getProducts();
this.categories = await Model.getCategories();
this.brands = await Model.getBrands();
View.renderProductList(this.products, this.handleEdit.bind(this), this.handleDelete.bind(this));
// Кнопка "Добавить товар"
document.getElementById('addProductBtn').onclick = () => {
View.showProductModal(
{ categories: this.categories, brands: this.brands },
this.handleAdd.bind(this)
);
};
},
async handleAdd(productData, modal) {
await Model.addProduct(productData);
modal.hide();
await this.refresh();
},
async handleEdit(productId) {
const product = await Model.getProductById(productId);
View.showProductModal(
{ product, categories: this.categories, brands: this.brands },
async (formData, modal) => {
await Model.updateProduct(productId, formData);
modal.hide();
await this.refresh();
}
);
},
async handleDelete(productId) {
if (confirm('Удалить товар?')) {
await Model.deleteProduct(productId);
await this.refresh();
}
},
async refresh() {
this.products = await Model.getProducts();
View.renderProductList(this.products, this.handleEdit.bind(this), this.handleDelete.bind(this));
}
};

View File

@@ -1,46 +0,0 @@
// js/model.js
const API_URL = 'http://localhost:5000';
export default {
// --- PRODUCTS ---
async getProducts() {
const res = await fetch(`${API_URL}/products?_expand=category&_expand=brand`);
return res.json();
},
async getProductById(id) {
const res = await fetch(`${API_URL}/products/${id}`);
return res.json();
},
async addProduct(product) {
const res = await fetch(`${API_URL}/products`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product)
});
return res.json();
},
async updateProduct(id, product) {
const res = await fetch(`${API_URL}/products/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product)
});
return res.json();
},
async deleteProduct(id) {
await fetch(`${API_URL}/products/${id}`, { method: 'DELETE' });
},
// --- CATEGORIES ---
async getCategories() {
const res = await fetch(`${API_URL}/categories`);
return res.json();
},
// --- BRANDS ---
async getBrands() {
const res = await fetch(`${API_URL}/brands`);
return res.json();
}
};

View File

@@ -1,124 +0,0 @@
// js/view.js
export default {
renderProductList(products, onEdit, onDelete) {
const list = document.getElementById('productsList');
list.innerHTML = '';
products.forEach(product => {
const col = document.createElement('div');
col.className = 'col';
// Если image не начинается с "data:" — выводим как есть (старые товары), иначе как base64
const imgSrc = product.image?.startsWith('data:') ? product.image : (product.image || 'images/no-image.png');
col.innerHTML = `
<div class="card h-100">
<img src="${imgSrc}" class="card-img-top" alt="${product.name}" style="width: 100%; height: 300px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title">${product.name}</h5>
<p class="card-text">${product.price} руб</p>
<p class="card-text">
<small class="text-muted">Категория: ${product.category?.name || '-'}</small><br>
<small class="text-muted">Бренд: ${product.brand?.name || '-'}</small>
</p>
<button class="btn btn-primary btn-sm me-2 edit-btn"><i class="bi bi-pencil"></i> Редактировать</button>
<button class="btn btn-danger btn-sm delete-btn"><i class="bi bi-trash"></i> Удалить</button>
</div>
</div>
`;
// Навесить обработчики
col.querySelector('.edit-btn').onclick = () => onEdit(product.id);
col.querySelector('.delete-btn').onclick = () => onDelete(product.id);
list.appendChild(col);
});
},
showProductModal({ product = {}, categories = [], brands = [] }, onSubmit) {
const modalContent = document.getElementById('productModalContent');
modalContent.innerHTML = `
<div class="modal-header">
<h5 class="modal-title">${product.id ? 'Редактировать' : 'Добавить'} товар</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="productForm">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Название товара</label>
<input type="text" class="form-control" name="name" value="${product.name || ''}" required>
</div>
<div class="mb-3">
<label class="form-label">Цена (руб)</label>
<input type="number" class="form-control" name="price" value="${product.price || ''}" required>
</div>
<div class="mb-3">
<label class="form-label">Изображение</label>
<input type="file" class="form-control" name="image" accept="image/*" ${product.id ? "" : "required"}>
<img id="previewImage" src="${product.image || ''}" style="max-width:100%;margin-top:10px;${product.image ? '' : 'display:none;'}"/>
</div>
<div class="mb-3">
<label class="form-label">Категория</label>
<select class="form-select" name="categoryId" required>
<option value="">Выберите категорию</option>
${categories.map(cat => `
<option value="${cat.id}" ${product.categoryId == cat.id ? 'selected' : ''}>${cat.name}</option>
`).join('')}
</select>
</div>
<div class="mb-3">
<label class="form-label">Бренд</label>
<select class="form-select" name="brandId" required>
<option value="">Выберите бренд</option>
${brands.map(br => `
<option value="${br.id}" ${product.brandId == br.id ? 'selected' : ''}>${br.name}</option>
`).join('')}
</select>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">${product.id ? 'Сохранить' : 'Добавить'}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
</div>
</form>
`;
// Показываем модалку через Bootstrap JS
const modal = new bootstrap.Modal(document.getElementById('productModal'));
modal.show();
// Предпросмотр выбранной картинки
const fileInput = modalContent.querySelector('input[type="file"]');
const preview = modalContent.querySelector('#previewImage');
fileInput.onchange = () => {
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = e => {
preview.src = e.target.result;
preview.style.display = '';
};
reader.readAsDataURL(file);
}
};
// Обработка формы (с поддержкой base64-картинки)
document.getElementById('productForm').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const obj = Object.fromEntries(formData.entries());
obj.price = +obj.price;
obj.categoryId = +obj.categoryId;
obj.brandId = +obj.brandId;
if (fileInput.files.length) {
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function(event) {
obj.image = event.target.result; // base64
onSubmit(obj, modal);
};
reader.readAsDataURL(file);
} else {
obj.image = product.image || '';
onSubmit(obj, modal);
}
};
}
};

View File

@@ -1,99 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин</title>
<link rel="stylesheet" href="css/style.css" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
</head>
<body>
<header">
<div class="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" class="me-3" style="width: 200px; height: auto;">
<a href="newSite.html" class="text-decoration-none text-dark m-1">
<h1 class="display-4 h3 mt-3"><b>Название магазина</b></h1>
</a>
</div>
<navbar>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown"
data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul class="dropdown-menu" aria-labelledby="navigationDropdown">
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Account.html">Личный кабинет<i class="bi bi-person-circle ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Basket.html">Корзина<i class="bi bi-cart4 ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Order.html">Заказы<i class="bi bi-receipt ms-2"></i></a></li>
<li><a class="dropdown-item d-flex justify-content-between align-items-center"
href="Favorites.html">Избранное<i class="bi bi-heart-fill ms-2"></i></a></li>
</ul>
</div>
</navbar>
</header>
<main class="container">
<div class="d-flex justify-content-end my-4">
<button class="btn btn-success" id="addProductBtn">
<i class="bi bi-plus-circle"></i> Добавить товар
</button>
</div>
<!-- Модальное окно для формы добавления/редактирования -->
<div class="modal fade" id="productModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content" id="productModalContent">
<!-- JS сам отрисует тут форму через View.showProductForm() -->
</div>
</div>
</div>
<h2 class="text-center my-3">Рекомендуемые товары:</h2>
<div class="row row-cols-1 row-cols-md-3 g-4" id="productsList">
<!-- Здесь будут карточки товаров (рендерит JS) -->
</div>
</main>
<footer class="container mt-5">
<div class="bg-light p-4">
<h5>Помощь:</h5>
<div class="d-flex flex-wrap">
<div class="d-flex align-items-center me-4 mb-3">
<i class="bi bi-telephone-fill me-2"></i>
<a href="#" class="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" class="me-2" style="width: 24px; height: 24px;">
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank"
class="text-decoration-none text-dark">vk.com</a>
</div>
<div class="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" class="me-2" style="width: 24px; height: 24px;">
<a href="#" class="text-decoration-none text-dark">tg.me</a>
</div>
<div class="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" class="me-2" style="width: 24px; height: 24px;">
<a href="mailto:ozon-zon-zon@mail.joke"
class="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
<div class="text-center mt-4">
<img src="images/бананы.jpg" alt="Бананы" class="img-fluid">
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script type="module">
import Controller from './js/controller.js';
Controller.init();
</script>
</body>
</html>

1312
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
"dev": "npm-run-all --parallel start json-server",
"start": "vite",
"build": "vite build",
"server": "http-server -p 3000 ./dist/",
@@ -14,18 +15,23 @@
"license": "ISC",
"description": "",
"dependencies": {
"bootstrap":"5.3.3",
"bootstrap-icons": "1.11.3"
"bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"http-server": "14.1.1",
"vite": "6.2.0",
"npm-run-all": "4.1.5",
"@vitejs/plugin-react": "^4.4.1",
"eslint": "8.56.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.0.2",
"eslint-plugin-html": "8.1.2",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.3",
"eslint-plugin-html": "8.1.2"
"http-server": "14.1.1",
"json-server": "^1.0.0-beta.3",
"npm-run-all": "4.1.5",
"vite": "6.2.0"
}
}

26
src/App.jsx Normal file
View File

@@ -0,0 +1,26 @@
import React, { useState } from 'react';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
import MainPage from "./pages/MainPage";
import BasketPage from "./pages/BasketPage";
import FavoritesPage from "./pages/FavoritesPage";
import OrderPage from "./pages/OrderPage";
import AccountPage from "./pages/AccountPage";
export default function App() {
return (
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/basket" element={<BasketPage />} />
<Route path="/favorites" element={<FavoritesPage />} />
<Route path="/orders" element={<OrderPage />} />
<Route path="/account" element={<AccountPage />} />
</Routes>
<Footer />
</BrowserRouter>
);
}

29
src/components/Footer.jsx Normal file
View File

@@ -0,0 +1,29 @@
import React from "react";
export default function Footer() {
return (
<footer className="container mt-5">
<div className="bg-light p-4">
<h5>Помощь:</h5>
<div className="d-flex flex-wrap">
<div className="d-flex align-items-center me-4 mb-3">
<i className="bi bi-telephone-fill me-2"></i>
<a href="#" className="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div className="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" className="me-2" style={{ width: 24, height: 24 }} />
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank" className="text-decoration-none text-dark">vk.com</a>
</div>
<div className="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" className="me-2" style={{ width: 24, height: 24 }} />
<a href="#" className="text-decoration-none text-dark">tg.me</a>
</div>
<div className="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" className="me-2" style={{ width: 24, height: 24 }} />
<a href="mailto:ozon-zon-zon@mail.joke" className="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
</footer>
);
}

28
src/components/Header.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React from "react";
import { Link } from "react-router-dom";
export default function Header() {
return (
<header>
<div className="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" className="me-3" style={{ width: 200, height: "auto" }} />
<Link to="/" className="text-decoration-none text-dark m-1">
<h1 className="display-4 h3 mt-3"><b>Название магазина</b></h1>
</Link>
</div>
<nav>
<div className="dropdown">
<button className="btn btn-primary dropdown-toggle ms-3" type="button" id="navigationDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul className="dropdown-menu" aria-labelledby="navigationDropdown">
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/account">Личный кабинет<i className="bi bi-person-circle ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/basket">Корзина<i className="bi bi-cart4 ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/orders">Заказы<i className="bi bi-receipt ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/favorites">Избранное<i className="bi bi-heart-fill ms-2"></i></Link></li>
</ul>
</div>
</nav>
</header>
);
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
export default function ProductCard({ product, onEdit, onDelete, onAddToFavorites }) {
return (
<div className="col">
<div className="card h-100">
{product.image && (
<img src={product.image} className="card-img-top" alt={product.name} style={{ height: 300, objectFit: 'cover' }} />
)}
<div className="card-body">
<h5 className="card-title">{product.name}</h5>
<p className="card-text">Цена: {product.price} </p>
<button className="btn btn-sm btn-outline-primary me-2" onClick={() => onEdit(product)}>Изменить</button>
<button className="btn btn-sm btn-outline-danger me-2" onClick={() => onDelete(product.id)}>Удалить</button>
<button className="btn btn-sm btn-outline-success" onClick={() => onAddToFavorites(product)}>
<i className="bi bi-heart"></i> В избранное
</button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,72 @@
import React, { useState, useEffect } from 'react';
export default function ProductForm({ initial, onSave, onCancel }) {
const [form, setForm] = useState({ name: '', price: '', image: '' });
useEffect(() => {
if (initial) {
setForm({
name: initial.name || '',
price: initial.price || '',
image: initial.image || ''
});
} else {
setForm({ name: '', price: '', image: '' });
}
}, [initial]);
const handleChange = e => setForm({ ...form, [e.target.name]: e.target.value });
const handleSubmit = e => {
e.preventDefault();
onSave({ ...initial, name: form.name, price: Number(form.price), image: form.image });
setForm({ name: '', price: '', image: '' });
};
return (
<form onSubmit={handleSubmit} className="card p-3 mb-4">
<div className="mb-2">
<label className="form-label">Название</label>
<input
name="name"
value={form.name}
onChange={handleChange}
required
className="form-control"
/>
</div>
<div className="mb-2">
<label className="form-label">Цена</label>
<input
name="price"
value={form.price}
onChange={handleChange}
type="number"
required
className="form-control"
/>
</div>
<div className="mb-2">
<label className="form-label">Ссылка на картинку</label>
<input
name="image"
value={form.image}
onChange={handleChange}
className="form-control"
placeholder="Например: images/glasses.jpg или https://example.com/photo.jpg"
/>
</div>
{form.image && (
<div className="mb-2 text-center">
<img
src={form.image}
alt="Превью"
style={{ maxHeight: 120, objectFit: 'contain', maxWidth: "100%" }}
/>
</div>
)}
<button type="submit" className="btn btn-primary mb-2" style={{ width: 'auto' }}>Сохранить</button>
<button type="button" className="btn btn-secondary" style={{ width: 'auto' }} onClick={onCancel}>Отмена</button>
</form>
);
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import ProductCard from './ProductCard';
export default function ProductList({ products, onEdit, onDelete, onAddToFavorites }) {
return (
<div className="row row-cols-1 row-cols-md-3 g-4">
{products.map(prod => (
<ProductCard
key={prod.id}
product={prod}
onEdit={onEdit}
onDelete={onDelete}
onAddToFavorites={onAddToFavorites}
/>
))}
</div>
);
}

35
src/hooks/useBasket.jsx Normal file
View File

@@ -0,0 +1,35 @@
import { useState, useEffect } from "react";
export default function useBasket() {
const [basket, setBasket] = useState([]);
useEffect(() => {
fetch('http://localhost:5000/basket')
.then(res => res.json())
.then(setBasket);
}, []);
const addToBasket = async (item) => {
const res = await fetch('http://localhost:5000/basket', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
});
const newItem = await res.json();
setBasket([...basket, newItem]);
};
const removeFromBasket = async (id) => {
await fetch(`http://localhost:5000/basket/${id}`, { method: 'DELETE' });
setBasket(basket.filter(item => item.id !== id));
};
const clearBasket = async () => {
for (let item of basket) {
await fetch(`http://localhost:5000/basket/${item.id}`, { method: 'DELETE' });
}
setBasket([]);
};
return { basket, addToBasket, removeFromBasket, clearBasket };
}

View File

@@ -0,0 +1,28 @@
import { useState, useEffect } from "react";
export default function useFavorites() {
const [favorites, setFavorites] = useState([]);
useEffect(() => {
fetch('http://localhost:5000/favorites')
.then(res => res.json())
.then(setFavorites);
}, []);
const addToFavorites = async (item) => {
const res = await fetch('http://localhost:5000/favorites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
});
const newItem = await res.json();
setFavorites([...favorites, newItem]);
};
const removeFromFavorites = async (id) => {
await fetch(`http://localhost:5000/favorites/${id}`, { method: 'DELETE' });
setFavorites(favorites.filter(item => item.id !== id));
};
return { favorites, addToFavorites, removeFromFavorites };
}

26
src/hooks/useOrders.jsx Normal file
View File

@@ -0,0 +1,26 @@
import { useState, useEffect } from "react";
export default function useOrders() {
const [orders, setOrders] = useState([]);
useEffect(() => {
fetch('http://localhost:5000/orders')
.then(res => res.json())
.then(setOrders);
}, []);
const addOrder = async (order) => {
const res = await fetch('http://localhost:5000/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order)
});
const newOrder = await res.json();
setOrders([...orders, newOrder]);
};
const inProcess = orders.filter(o => o.status === "in-process");
const completed = orders.filter(o => o.status === "completed");
return { orders, addOrder, inProcess, completed };
}

33
src/hooks/useProducts.jsx Normal file
View File

@@ -0,0 +1,33 @@
import { useState, useEffect } from 'react';
export default function useProducts() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch('http://localhost:5000/products')
.then(res => res.json())
.then(setProducts);
}, []);
const add = async prod => {
const res = await fetch('http://localhost:5000/products', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prod)
});
const newProd = await res.json();
setProducts([...products, newProd]);
};
const update = async prod => {
await fetch(`http://localhost:5000/products/${prod.id}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prod)
});
setProducts(products.map(p => p.id === prod.id ? prod : p));
};
const remove = async id => {
await fetch(`http://localhost:5000/products/${id}`, { method: 'DELETE' });
setProducts(products.filter(p => p.id !== id));
};
return { products, add, update, remove };
}

24
src/hooks/useProfile.jsx Normal file
View File

@@ -0,0 +1,24 @@
import { useState, useEffect } from "react";
export default function useProfile() {
const [profile, setProfile] = useState(null);
useEffect(() => {
fetch('http://localhost:5000/profile')
.then(res => res.json())
.then(setProfile);
}, []);
const updateProfile = async (newProfile) => {
// PATCH или PUT — по ситуации
const res = await fetch('http://localhost:5000/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newProfile)
});
const updated = await res.json();
setProfile(updated);
};
return { profile, updateProfile };
}

8
src/index.jsx Normal file
View File

@@ -0,0 +1,8 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
const container = document.getElementById("root");
createRoot(container).render(<App />);

76
src/pages/AccountPage.jsx Normal file
View File

@@ -0,0 +1,76 @@
import React, { useState } from "react";
import useProfile from "../hooks/useProfile";
export default function AccountPage() {
const { profile, updateProfile } = useProfile();
const [showEdit, setShowEdit] = useState(false);
const [form, setForm] = useState(null);
if (!profile) {
return <div className="text-center">Загрузка...</div>;
}
const handleEdit = () => {
setForm(profile);
setShowEdit(true);
};
const handleChange = e => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleSave = async e => {
e.preventDefault();
await updateProfile(form);
setShowEdit(false);
};
const handleCancel = () => setShowEdit(false);
return (
<div className="container mt-5">
<div className="card text-center mx-auto" style={{ maxWidth: 400, maxHeight: 400 }}>
<img src={profile.image} className="card-img-top" alt="Профиль" style={{ width: "100%", height: 300, objectFit: "cover" }} />
<div className="card-body">
<h3 className="card-title">{profile.firstName} {profile.lastName}</h3>
<button className="btn btn-primary" onClick={handleEdit}>
<i className="bi bi-pencil"></i> Редактировать профиль
</button>
</div>
</div>
{showEdit && (
<div className="modal d-block" tabIndex="-1" style={{ background: 'rgba(0,0,0,0.3)' }}>
<div className="modal-dialog">
<div className="modal-content">
<form onSubmit={handleSave}>
<div className="modal-header">
<h5 className="modal-title">Редактировать профиль</h5>
<button type="button" className="btn-close" onClick={handleCancel}></button>
</div>
<div className="modal-body">
<div className="mb-2">
<label className="form-label">Имя</label>
<input className="form-control" name="firstName" value={form.firstName} onChange={handleChange} required />
</div>
<div className="mb-2">
<label className="form-label">Фамилия</label>
<input className="form-control" name="lastName" value={form.lastName} onChange={handleChange} required />
</div>
<div className="mb-2">
<label className="form-label">URL аватара</label>
<input className="form-control" name="image" value={form.image} onChange={handleChange} />
</div>
</div>
<div className="modal-footer">
<button type="submit" className="btn btn-success">Сохранить</button>
<button type="button" className="btn btn-secondary" onClick={handleCancel}>Отмена</button>
</div>
</form>
</div>
</div>
</div>
)}
</div>
);
}

45
src/pages/BasketPage.jsx Normal file
View File

@@ -0,0 +1,45 @@
import React from "react";
import useBasket from "../hooks/useBasket";
import useOrders from "../hooks/useOrders";
export default function BasketPage() {
const { basket, removeFromBasket, clearBasket } = useBasket();
const { addOrder } = useOrders();
const handleCheckout = () => {
if (basket.length === 0) {
alert("Корзина пуста");
return;
}
// Сформируем заказ
addOrder({
items: basket,
status: "in-process"
});
clearBasket(); // Очищаем корзину
alert("Заказ оформлен!");
};
return (
<main className="container d-flex justify-content-center align-items-center" style={{ minHeight: "60vh" }}>
<div className="card p-4 shadow" style={{ minWidth: "60vw" }}>
<h2 className="text-center">Корзина</h2>
<ul className="list-group list-group-flush">
{basket.map(item => (
<li className="list-group-item d-flex align-items-center" key={item.id}>
<img src={item.image} alt={item.name} style={{ width: 100, height: 100 }} className="me-3" />
{item.name} <span className="ms-auto">{item.price} руб.</span>
<button onClick={() => removeFromBasket(item.id)} className="btn btn-danger ms-2">Удалить</button>
</li>
))}
</ul>
<div className="text-center mt-3">
<button className={`btn w-100 ${basket.length === 0 ? "btn-secondary" : "btn-success"}`} onClick={handleCheckout}>
Оплатить {}
</button>
</div>
</div>
</main>
);
}

View File

@@ -0,0 +1,34 @@
import React from "react";
import useBasket from "../hooks/useBasket";
import useFavorites from "../hooks/useFavorites";
export default function FavoritesPage() {
const { favorites, removeFromFavorites } = useFavorites();
const { basket, addToBasket } = useBasket();
const handleAddToBasket = (item) => {
addToBasket(item);
removeFromFavorites(item.id);
};
return (
<div className="container mt-4">
<h2 className="mb-4">Избранное</h2>
<div className="row row-cols-1 row-cols-md-2 g-4">
{favorites.map(item => (
<div className="col" key={item.id}>
<div className="card mx-auto" style={{ width: "70%" }}>
<img src={item.image} className="card-img-top" alt={item.name} style={{ width: "100%", height: 300, objectFit: "cover" }} />
<div className="card-body text-center">
<h5 className="card-title">{item.name}</h5>
<p className="card-text">{item.price}</p>
<button className="btn btn-success me-2" onClick={() => handleAddToBasket(item)}>В корзину</button>
<button onClick={() => removeFromFavorites(item.id)} className="btn btn-danger">Удалить</button>
</div>
</div>
</div>
))}
</div>
</div>
);
}

42
src/pages/MainPage.jsx Normal file
View File

@@ -0,0 +1,42 @@
import React, { useState } from "react";
import useProducts from "../hooks/useProducts";
import useFavorites from "../hooks/useFavorites";
import ProductList from "../components/ProductList";
import ProductForm from "../components/ProductForm";
export default function MainPage() {
const { products, add, update, remove } = useProducts();
const { favorites, addToFavorites } = useFavorites();
const [editing, setEditing] = useState(null);
const [showForm, setShowForm] = useState(false);
const handleAdd = () => { setEditing(null); setShowForm(true); };
const handleEdit = prod => { setEditing(prod); setShowForm(true); };
const handleDelete = id => remove(id);
const handleSave = prod => {
editing ? update({ ...prod, id: editing.id }) : add(prod);
setShowForm(false);
};
const handleCancel = () => setShowForm(false);
const handleAddToFavorites = product => {
if (!favorites.some(fav => fav.id === product.id)) {
addToFavorites(product);
}
else alert('Товар уже в избранном!');
};
return (
<main className="container my-4">
<button className="btn btn-success mb-3" onClick={handleAdd}>Добавить товар</button>
{showForm && <ProductForm initial={editing} onSave={handleSave} onCancel={handleCancel} />}
<h2 className="text-center my-3">Рекомендуемые товары:</h2>
<ProductList
products={products}
onEdit={handleEdit}
onDelete={handleDelete}
onAddToFavorites={handleAddToFavorites}
/>
</main>
);
}

58
src/pages/OrderPage.jsx Normal file
View File

@@ -0,0 +1,58 @@
import React from "react";
import useOrders from "../hooks/useOrders";
export default function OrderPage() {
const { inProcess, completed } = useOrders();
return (
<div className="container mt-4">
<h1 className="text-center">Заказы</h1>
<div className="row">
<div className="col-md-6">
<div className="card shadow-sm">
<div className="card-header bg-warning text-dark">
<h2 className="h5 m-0">В процессе</h2>
</div>
<div className="card-body">
{inProcess.length === 0 ? (
<div className="text-center text-muted">Нет заказов</div>
) : (
inProcess.map(order => (
<ul className="list-group list-group-flush mb-3" key={order.id}>
{order.items.map((item, idx) => (
<li className="list-group-item d-flex align-items-center mb-2" key={idx}>
<img src={item.image} className="me-2" style={{ width: 120, height: 120, objectFit: "cover" }} alt={item.name} /> {item.name}
</li>
))}
</ul>
))
)}
</div>
</div>
</div>
<div className="col-md-6">
<div className="card shadow-sm">
<div className="card-header bg-success text-white">
<h2 className="h5 m-0">Завершённые</h2>
</div>
<div className="card-body">
{completed.length === 0 ? (
<div className="text-center text-muted">Нет завершённых заказов</div>
) : (
completed.map(order => (
<ul className="list-group list-group-flush mb-3" key={order.id}>
{order.items.map((item, idx) => (
<li className="list-group-item d-flex align-items-center mb-2" key={idx}>
<img src={item.image} className="me-2" style={{ width: 120, height: 120, objectFit: "cover" }} alt={item.name} /> {item.name}
</li>
))}
</ul>
))
)}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,16 +1,28 @@
import { resolve } from 'path';
import { defineConfig } from "vite";
import react from '@vitejs/plugin-react';
export default defineConfig({
build: {
rollupOptions: {
input: {
root: '.',
plugins: [react()],
build: {
outDir: 'dist',
rollupOptions: {
input: {
main: resolve(__dirname, "newSite.html"),
basketPage: resolve(__dirname, "Basket.html"),
favouritesPage: resolve(__dirname, "Favorites.html"),
orderPage: resolve(__dirname, "Order.html"),
accountPage: resolve(__dirname, "Account.html"),
},
},
},
},
},
server: {
open: '/',
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
});

BIN
Отчет4.docx Normal file

Binary file not shown.

BIN
Отчет5.docx Normal file

Binary file not shown.

BIN
Отчет6.docx Normal file

Binary file not shown.