Compare commits
2 Commits
6d23bf41ba
...
lab6
| Author | SHA1 | Date | |
|---|---|---|---|
| b7aa56e629 | |||
| ca3a9c8aee |
BIN
img/рикрол.mp4
Normal file
BIN
img/рикрол.mp4
Normal file
Binary file not shown.
35
src/App.css
35
src/App.css
@@ -17,8 +17,22 @@ body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Главный контейнер для контента */
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Стили для страницы ошибки */
|
||||
.error-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Стили для навигации */
|
||||
@@ -80,4 +94,25 @@ main {
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
/* Стили для панели сортировки */
|
||||
.sort-panel {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.sort-select {
|
||||
border-color: #00264d;
|
||||
}
|
||||
|
||||
.sort-select:focus {
|
||||
border-color: #00264d;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 38, 77, 0.25);
|
||||
}
|
||||
|
||||
.sort-info {
|
||||
background-color: #e3f2fd;
|
||||
border-left: 4px solid #00264d;
|
||||
}
|
||||
18
src/App.jsx
18
src/App.jsx
@@ -8,6 +8,7 @@ import CatalogPage from './pages/CatalogPage.jsx';
|
||||
import ContactsPage from './pages/ContactsPage.jsx';
|
||||
import LikesPage from './pages/LikesPage.jsx';
|
||||
import BasketPage from './pages/BasketPage.jsx';
|
||||
import ErrorPage from './pages/ErrorPage.jsx';
|
||||
|
||||
import Footer from './components/Footer.jsx';
|
||||
import Navbar from './components/Navbar.jsx';
|
||||
@@ -20,13 +21,16 @@ export default function App() {
|
||||
<ToastContainer />
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<IndexPage />} />
|
||||
<Route path="/catalog" element={<CatalogPage />} />
|
||||
<Route path="/contacts" element={<ContactsPage />} />
|
||||
<Route path="/likes" element={<LikesPage />} />
|
||||
<Route path="/basket" element={<BasketPage />} />
|
||||
</Routes>
|
||||
<main style={{ flex: 1 }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<IndexPage />} />
|
||||
<Route path="/catalog" element={<CatalogPage />} />
|
||||
<Route path="/contacts" element={<ContactsPage />} />
|
||||
<Route path="/likes" element={<LikesPage />} />
|
||||
<Route path="/basket" element={<BasketPage />} />
|
||||
<Route path="*" element={<ErrorPage/>}/>
|
||||
</Routes>
|
||||
</main>
|
||||
<Footer />
|
||||
</BrowserRouter>
|
||||
</LikesProvider>
|
||||
|
||||
@@ -19,6 +19,23 @@ export default function CatalogPage() {
|
||||
} = useProducts();
|
||||
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [sortBy, setSortBy] = useState(''); // '' - без сортировки, 'price_asc' - по возрастанию, 'price_desc' - по убыванию
|
||||
|
||||
// Функция для сортировки товаров
|
||||
const getSortedProducts = () => {
|
||||
const productsCopy = [...products];
|
||||
|
||||
switch (sortBy) {
|
||||
case 'price_asc':
|
||||
return productsCopy.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
|
||||
case 'price_desc':
|
||||
return productsCopy.sort((a, b) => parseFloat(b.price) - parseFloat(a.price));
|
||||
default:
|
||||
return productsCopy;
|
||||
}
|
||||
};
|
||||
|
||||
const sortedProducts = getSortedProducts();
|
||||
|
||||
const handleAddProduct = async (productData) => {
|
||||
try {
|
||||
@@ -47,6 +64,10 @@ export default function CatalogPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSortChange = (e) => {
|
||||
setSortBy(e.target.value);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<main className="container my-4">
|
||||
@@ -83,6 +104,51 @@ export default function CatalogPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Панель сортировки */}
|
||||
<div className="row mb-4">
|
||||
<div className="col-md-6">
|
||||
<div className="card border-0 shadow-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<label htmlFor="sortSelect" className="col-form-label">
|
||||
<i className="bi bi-sort-down me-2"></i>Сортировка:
|
||||
</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<select
|
||||
id="sortSelect"
|
||||
className="form-select"
|
||||
value={sortBy}
|
||||
onChange={handleSortChange}
|
||||
>
|
||||
<option value="">Без сортировки</option>
|
||||
<option value="price_asc">Цена по возрастанию</option>
|
||||
<option value="price_desc">Цена по убыванию</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о сортировке */}
|
||||
<div className="col-md-6">
|
||||
{sortBy && (
|
||||
<div className="card border-0 shadow-sm">
|
||||
<div className="card-body py-2">
|
||||
<small className="text-muted">
|
||||
<i className="bi bi-info-circle me-1"></i>
|
||||
{sortBy === 'price_asc' && 'Сортировка: от дешевых к дорогим'}
|
||||
{sortBy === 'price_desc' && 'Сортировка: от дорогих к дешевым'}
|
||||
{sortedProducts.length > 0 && ` (${sortedProducts.length} товаров)`}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAddForm && (
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
@@ -99,7 +165,7 @@ export default function CatalogPage() {
|
||||
|
||||
{/* 3 карточки в ряд на всех экранах кроме мобильных */}
|
||||
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-5">
|
||||
{products.map(product => (
|
||||
{sortedProducts.map(product => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
@@ -113,7 +179,7 @@ export default function CatalogPage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{products.length === 0 && !showAddForm && (
|
||||
{sortedProducts.length === 0 && !showAddForm && (
|
||||
<div className="text-center py-5">
|
||||
<h3>Товаров пока нет</h3>
|
||||
<p className="text-muted">Добавьте первый товар в каталог</p>
|
||||
|
||||
28
src/Pages/ErrorPage.jsx
Normal file
28
src/Pages/ErrorPage.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
export default function ErrorPage() {
|
||||
return (
|
||||
<div className="error-page" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '60vh',
|
||||
textAlign: 'center',
|
||||
padding: '2rem'
|
||||
}}>
|
||||
<h1>404 - Страница не найдена</h1>
|
||||
<p>Извините, запрашиваемая страница не существует.</p>
|
||||
<video
|
||||
src="./img/рикрол.mp4"
|
||||
autoPlay
|
||||
loop
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
borderRadius: '8px',
|
||||
marginTop: '1rem'
|
||||
}}
|
||||
>
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user