done without .docx
This commit is contained in:
31
db.json
31
db.json
@@ -3,23 +3,38 @@
|
||||
{
|
||||
"id": "1a54",
|
||||
"name": "новый товар",
|
||||
"price": 102
|
||||
"price": 102,
|
||||
"image": "images/fork.jpg"
|
||||
},
|
||||
{
|
||||
"id": "f4f8",
|
||||
"name": " товарчик",
|
||||
"price": 111
|
||||
"name": "товарчик",
|
||||
"price": 111,
|
||||
"image": "images/chery.jpg"
|
||||
},
|
||||
{
|
||||
"id": "0fda",
|
||||
"name": "ложка",
|
||||
"price": 48,
|
||||
"image": "images/bananas.jpg"
|
||||
}
|
||||
],
|
||||
"basket": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Очки",
|
||||
"price": 349,
|
||||
"image": "images/glasses.jpg"
|
||||
"id": "0fda",
|
||||
"name": "ложка",
|
||||
"price": 48,
|
||||
"image": "images/bananas.jpg"
|
||||
}
|
||||
],
|
||||
"favorites": [
|
||||
{
|
||||
"id": "1a54",
|
||||
"name": "новый товар",
|
||||
"price": 102,
|
||||
"image": "images/fork.jpg"
|
||||
}
|
||||
],
|
||||
"favorites": [],
|
||||
"orders": [
|
||||
{
|
||||
"id": "1",
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ProductCard({ product, onEdit, onDelete }) {
|
||||
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" onClick={() => onDelete(product.id)}>Удалить</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>
|
||||
|
||||
@@ -1,30 +1,70 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function ProductForm({ initial, onSave, onCancel }) {
|
||||
const [form, setForm] = useState({ name: '', price: '' });
|
||||
const [form, setForm] = useState({ name: '', price: '', image: '' });
|
||||
|
||||
useEffect(() => {
|
||||
if (initial) setForm({ name: initial.name, price: initial.price });
|
||||
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) });
|
||||
setForm({ name: '', price: '' });
|
||||
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" />
|
||||
<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" />
|
||||
<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>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import React from 'react';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
export default function ProductList({ products, onEdit, onDelete }) {
|
||||
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} />
|
||||
<ProductCard
|
||||
key={prod.id}
|
||||
product={prod}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onAddToFavorites={onAddToFavorites}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,6 @@ export default function useBasket() {
|
||||
};
|
||||
|
||||
const clearBasket = async () => {
|
||||
// Очищаем корзину полностью
|
||||
for (let item of basket) {
|
||||
await fetch(`http://localhost:5000/basket/${item.id}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export default function useOrders() {
|
||||
setOrders([...orders, newOrder]);
|
||||
};
|
||||
|
||||
// Можно реализовать фильтрацию прямо в хуке
|
||||
const inProcess = orders.filter(o => o.status === "in-process");
|
||||
const completed = orders.filter(o => o.status === "completed");
|
||||
|
||||
|
||||
@@ -2,23 +2,75 @@ import React, { useState } from "react";
|
||||
import useProfile from "../hooks/useProfile";
|
||||
|
||||
export default function AccountPage() {
|
||||
const { profile } = useProfile();
|
||||
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">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,25 @@
|
||||
import React from "react";
|
||||
// импортируй кастомный хук для корзины, который ты сам реализуешь
|
||||
import useBasket from "../hooks/useBasket";
|
||||
import useOrders from "../hooks/useOrders";
|
||||
|
||||
export default function BasketPage() {
|
||||
|
||||
const { basket, addToBasket, removeFromBasket, clearBasket } = useBasket();
|
||||
// const { basket, removeFromBasket, checkout } = useBasket();
|
||||
// Ниже пример статики, замени на динамику после реализации useBasket
|
||||
const { basket, removeFromBasket, clearBasket } = useBasket();
|
||||
const { addOrder } = useOrders();
|
||||
|
||||
// const basket = [
|
||||
// { id: 1, name: "Очки", price: 349, image: "images/glasses.jpg" },
|
||||
// { id: 2, name: "Chery Tiggo 7 Pro Max", price: 5, image: "images/chery.jpg" },
|
||||
// { id: 3, name: "Ванадий", price: 2099, image: "images/vanadiy.jpg" },
|
||||
// ];
|
||||
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" }}>
|
||||
@@ -28,7 +35,7 @@ export default function BasketPage() {
|
||||
))}
|
||||
</ul>
|
||||
<div className="text-center mt-3">
|
||||
<button className="btn btn-success w-100">
|
||||
<button className="btn btn-success w-100" onClick={handleCheckout}>
|
||||
Оплатить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from "react";
|
||||
import useBasket from "../hooks/useBasket";
|
||||
import useFavorites from "../hooks/useFavorites";
|
||||
|
||||
export default function FavoritesPage() {
|
||||
const { favorites, addToFavorites, removeFromFavorites } = useFavorites();
|
||||
// Ниже пример статических карточек
|
||||
const { favorites, removeFromFavorites } = useFavorites();
|
||||
const { basket, addToBasket } = useBasket();
|
||||
|
||||
// const favorites = [
|
||||
// { id: 1, name: "Женщина", price: "бесценна", image: "images/masha.jpg" },
|
||||
// { id: 2, name: "Отвертка", price: "219 руб", image: "images/screwdriver.jpg" },
|
||||
// ];
|
||||
const handleAddToBasket = (item) => {
|
||||
addToBasket(item);
|
||||
removeFromFavorites(item.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
@@ -21,6 +22,7 @@ export default function FavoritesPage() {
|
||||
<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>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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);
|
||||
|
||||
@@ -17,12 +19,24 @@ export default function MainPage() {
|
||||
};
|
||||
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} />
|
||||
<ProductList
|
||||
products={products}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onAddToFavorites={handleAddToFavorites}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,19 +2,7 @@ import React from "react";
|
||||
import useOrders from "../hooks/useOrders";
|
||||
|
||||
export default function OrderPage() {
|
||||
|
||||
const { orders, addOrder, inProcess, completed } = useOrders();
|
||||
// Пример статики
|
||||
|
||||
// const inProcess = [
|
||||
// { id: 1, name: "Ложка", image: "images/spoon.jpg" },
|
||||
// { id: 2, name: "Вилка", image: "images/fork.jpg" },
|
||||
// { id: 3, name: "Нож", image: "images/knife.jpg" },
|
||||
// ];
|
||||
// const completed = [
|
||||
// { id: 4, name: "Утюг", image: "images/iron.jpg" },
|
||||
// { id: 5, name: "Бананы", image: "images/bananas.jpg" },
|
||||
// ];
|
||||
const { inProcess, completed } = useOrders();
|
||||
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
@@ -26,13 +14,19 @@ export default function OrderPage() {
|
||||
<h2 className="h5 m-0">В процессе</h2>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-group list-group-flush">
|
||||
{inProcess.map(item => (
|
||||
<li className="list-group-item d-flex align-items-center mb-2" key={item.id}>
|
||||
<img src={item.image} className="me-2" style={{ width: 200, height: 200 }} alt={item.name} /> {item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{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>
|
||||
@@ -42,13 +36,19 @@ export default function OrderPage() {
|
||||
<h2 className="h5 m-0">Завершённые</h2>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-group list-group-flush">
|
||||
{completed.map(item => (
|
||||
<li className="list-group-item d-flex align-items-center mb-2" key={item.id}>
|
||||
<img src={item.image} className="me-2" style={{ width: 200, height: 200 }} alt={item.name} /> {item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{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>
|
||||
|
||||
Reference in New Issue
Block a user