Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
616c03e397 | ||
| d1b2cea66c | |||
| 311491d554 | |||
| 5fa65ad591 | |||
|
|
9c80373c59 | ||
|
|
49fe7787f3 | ||
|
|
65407801d3 | ||
|
|
0f37299eab | ||
|
|
f09fa6e113 |
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Зависимости Node.js
|
||||
/node_modules/
|
||||
|
||||
# Результаты сборки Vite
|
||||
/dist/
|
||||
|
||||
# Логи
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
pnpm-debug.log*
|
||||
*.log
|
||||
|
||||
# Файлы окружения
|
||||
.env
|
||||
.env.*.local
|
||||
|
||||
# Кеши
|
||||
/.cache/
|
||||
.vite/
|
||||
|
||||
# IDE и редакторы
|
||||
.vscode/
|
||||
.idea/
|
||||
/*.sublime-workspace
|
||||
/*.sublime-project
|
||||
|
||||
# Системные файлы
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Отчеты об покрытии тестами
|
||||
/coverage/
|
||||
|
||||
# Порты и артефакты
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Прочее
|
||||
/.DS_Store
|
||||
14
db.json
Normal file
14
db.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"id": "1a54",
|
||||
"name": "новый товар",
|
||||
"price": 102
|
||||
},
|
||||
{
|
||||
"id": "f4f8",
|
||||
"name": " товарчик",
|
||||
"price": 111
|
||||
}
|
||||
]
|
||||
}
|
||||
15
newSite.html
Normal file
15
newSite.html
Normal 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>
|
||||
<main class="container">
|
||||
<h2 class="text-center my-3">Рекомендуемые товары:</h2>
|
||||
<div id="root"></div>
|
||||
</main>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
6281
package-lock.json
generated
Normal file
6281
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "ip",
|
||||
"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/",
|
||||
"prod": "npm-run-all build server",
|
||||
"lint": "eslint …",
|
||||
"json-server": "json-server --watch db.json --port 5000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"bootstrap": "5.3.3",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"http-server": "14.1.1",
|
||||
"json-server": "^1.0.0-beta.3",
|
||||
"npm-run-all": "4.1.5",
|
||||
"vite": "6.2.0"
|
||||
}
|
||||
}
|
||||
39
src/App.jsx
Normal file
39
src/App.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import useProducts from './hooks/useProducts';
|
||||
import ProductList from './components/ProductList';
|
||||
import ProductForm from './components/ProductForm';
|
||||
|
||||
export default function App() {
|
||||
const { products, add, update, remove } = useProducts();
|
||||
const [editing, setEditing] = useState(null);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
const [sortOrder, setSortOrder] = useState('asc');
|
||||
|
||||
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 sortedProducts = [...products].sort((a, b) =>
|
||||
sortOrder === 'asc'
|
||||
? a.price - b.price
|
||||
: b.price - a.price
|
||||
);
|
||||
|
||||
const toggleSortOrder = () => setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
|
||||
|
||||
return (
|
||||
<div className="container my-4">
|
||||
{/* <h1 className="mb-4">Каталог товаров</h1> */}
|
||||
<button className="btn btn-success mb-3" onClick={handleAdd}>Добавить товар</button>
|
||||
<button className="btn btn-outline-primary mb-3 ms-3" onClick={toggleSortOrder}> Сортировать по цене {sortOrder === 'asc' ? '▲' : '▼'}</button>
|
||||
{showForm && <ProductForm initial={editing} onSave={handleSave} onCancel={handleCancel} mode={editing ? 'edit' : 'add'} />}
|
||||
<ProductList products={sortedProducts} onEdit={handleEdit} onDelete={handleDelete} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
src/components/ProductCard.jsx
Normal file
16
src/components/ProductCard.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ProductCard({ product, onEdit, onDelete }) {
|
||||
return (
|
||||
<div className="col">
|
||||
<div className="card h-100">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/components/ProductForm.jsx
Normal file
35
src/components/ProductForm.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function ProductForm({ initial, onSave, onCancel, mode }) {
|
||||
const [form, setForm] = useState({ name: '', price: '' });
|
||||
|
||||
useEffect(() => {
|
||||
if (initial) setForm({ name: initial.name, price: initial.price });
|
||||
}, [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: '' });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="card p-3 mb-4">
|
||||
<h4 className="mb-3">
|
||||
{mode === 'edit' ? 'Редактирование товара' : 'Добавление товара'}
|
||||
</h4>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
12
src/components/ProductList.jsx
Normal file
12
src/components/ProductList.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
export default function ProductList({ products, onEdit, onDelete }) {
|
||||
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} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/hooks/useProducts.jsx
Normal file
33
src/hooks/useProducts.jsx
Normal 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 };
|
||||
}
|
||||
7
src/index.jsx
Normal file
7
src/index.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css' // опционально
|
||||
|
||||
const container = document.getElementById('root')
|
||||
createRoot(container).render(<App />)
|
||||
28
vite.config.js
Normal file
28
vite.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from "vite";
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
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: '/newSite.html',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
BIN
Отчет1.docx
Normal file
BIN
Отчет1.docx
Normal file
Binary file not shown.
BIN
Отчет2.docx
Normal file
BIN
Отчет2.docx
Normal file
Binary file not shown.
BIN
Отчет3.docx
Normal file
BIN
Отчет3.docx
Normal file
Binary file not shown.
BIN
Отчет4.docx
Normal file
BIN
Отчет4.docx
Normal file
Binary file not shown.
BIN
Отчет5.docx
Normal file
BIN
Отчет5.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user