mirror of
https://github.com/LOBSTERVOVA/Tennis-Site.git
synced 2026-04-17 17:40:49 +03:00
добавил блок с местами, теперь блоки с фильтрами и местами независимы
This commit is contained in:
@@ -97,7 +97,7 @@ public class SecurityConfig {
|
||||
.authorizeExchange(exchange -> exchange
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
// .csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,16 +19,5 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
@GetMapping("/login")
|
||||
public Mono<Rendering> loginPage() {
|
||||
Map<String, String> model = new HashMap<>();
|
||||
model.put("title", "Login");
|
||||
model.put("index", "login");
|
||||
return Mono.just(
|
||||
Rendering.view("template")
|
||||
.model(model)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
73
src/main/resources/static/css/places.css
Normal file
73
src/main/resources/static/css/places.css
Normal file
@@ -0,0 +1,73 @@
|
||||
.place-card {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
margin-bottom: 30px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.place-card:hover {
|
||||
transform: translateY(-10px);
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.place-img {
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.place-category {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.heart-counter {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255,255,255,0.9);
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.heart-counter i {
|
||||
color: #e74c3c;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.rating {
|
||||
color: #f1c40f;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.price-tag {
|
||||
font-weight: 700;
|
||||
font-size: 1.2rem;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.place-category {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.romantic-badge { background: #FD79A8FF; color: white; }
|
||||
.active-badge { background: #2ECC71FF; color: white; }
|
||||
.intellectual-badge { background: #3498DBFF; color: white; }
|
||||
.horror-badge { background: #2C3E50FF; color: white; }
|
||||
.cozy-badge { background: #E67E22FF; color: white; }
|
||||
.mystery-badge { background: #9B59B6FF; color: white; }
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "fonts.css";
|
||||
@import "main.css";
|
||||
@import "header.css";
|
||||
@import "places.css";
|
||||
|
||||
@@ -103,6 +103,7 @@ function initHeader($header){
|
||||
<!-- Форма входа -->
|
||||
<div class="tab-pane fade show active" id="login" role="tabpanel">
|
||||
<form id="loginForm" method="POST" action="/account/login">
|
||||
<input type="hidden" name="${csrf.parameterName}" value="${csrf.token}" />
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email или телефон</label>
|
||||
<input type="text" class="form-control" id="loginEmail" placeholder="example@mail.ru" required>
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
function toggleCategoryFilter(filter) {
|
||||
const btn = event.target.closest('.filter-btn');
|
||||
|
||||
if (btn.classList.contains('active')) {
|
||||
btn.classList.remove('active');
|
||||
} else {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function togglePriceFilter(filter) {
|
||||
const btn = event.target.closest('.filter-btn');
|
||||
|
||||
if (btn.classList.contains('active')) {
|
||||
btn.classList.remove('active');
|
||||
} else {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTimeFilter(filter) {
|
||||
const btn = event.target.closest('.filter-btn');
|
||||
|
||||
if (btn.classList.contains('active')) {
|
||||
btn.classList.remove('active');
|
||||
} else {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function initPlaceFilters($filtersContainer) {
|
||||
let currentFilters = {category: [], price: [], time: []};
|
||||
export function initPlaceFilters($filtersContainer) {
|
||||
console.log("init places filters");
|
||||
|
||||
const $filters = (`
|
||||
// Одна общая функция для всех фильтров
|
||||
function toggleFilter(event) {
|
||||
const $btn = $(event.currentTarget);
|
||||
$btn.toggleClass('active');
|
||||
|
||||
// Можно получить тип и значение фильтра
|
||||
const type = $btn.data('type');
|
||||
const value = $btn.data('value');
|
||||
|
||||
console.log(`Фильтр: ${type} = ${value}, активен: ${$btn.hasClass('active')}`);
|
||||
|
||||
// ОБНОВЛЯЕМ МАССИВ ФИЛЬТРОВ!
|
||||
if ($btn.hasClass('active')) {
|
||||
// Добавляем фильтр, если его еще нет
|
||||
if (!currentFilters[type].includes(value)) {
|
||||
currentFilters[type].push(value);
|
||||
}
|
||||
} else {
|
||||
// Удаляем фильтр
|
||||
currentFilters[type] = currentFilters[type].filter(item => item !== value);
|
||||
}
|
||||
|
||||
// Здесь можно обновить данные или сделать запрос
|
||||
// Триггерим событие изменения фильтров
|
||||
$(document).trigger('filtersChanged', [currentFilters]);
|
||||
}
|
||||
|
||||
const $filters = $(`
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="filter-card">
|
||||
@@ -40,22 +38,22 @@ function initPlaceFilters($filtersContainer) {
|
||||
<div class="col-md-6">
|
||||
<h5 class="mb-3">Тип свидания</h5>
|
||||
<div class="d-flex flex-wrap mb-4">
|
||||
<div class="filter-btn category-romantic active" onclick="toggleCategoryFilter('ROMANTIC')">
|
||||
<div class="filter-btn category-romantic" data-type="category" data-value="ROMANTIC">
|
||||
<i class="fas fa-heart me-2"></i>Романтическое
|
||||
</div>
|
||||
<div class="filter-btn category-active" onclick="toggleCategoryFilter('ACTIVE')">
|
||||
<div class="filter-btn category-active" data-type="category" data-value="ACTIVE">
|
||||
<i class="fas fa-hiking me-2"></i>Активное
|
||||
</div>
|
||||
<div class="filter-btn category-intellectual" onclick="toggleCategoryFilter('INTELLECTUAL')">
|
||||
<div class="filter-btn category-intellectual" data-type="category" data-value="INTELLECTUAL">
|
||||
<i class="fas fa-brain me-2"></i>Интеллектуальное
|
||||
</div>
|
||||
<div class="filter-btn category-horror" onclick="toggleCategoryFilter('HORROR')">
|
||||
<div class="filter-btn category-horror" data-type="category" data-value="HORROR">
|
||||
<i class="fas fa-ghost me-2"></i>Хоррор
|
||||
</div>
|
||||
<div class="filter-btn category-cozy" onclick="toggleCategoryFilter('COZY')">
|
||||
<div class="filter-btn category-cozy" data-type="category" data-value="COZY">
|
||||
<i class="fas fa-mug-hot me-2"></i>Уютное
|
||||
</div>
|
||||
<div class="filter-btn category-mystery" onclick="toggleCategoryFilter('MYSTERY')">
|
||||
<div class="filter-btn category-mystery" data-type="category" data-value="MYSTERY">
|
||||
<i class="fas fa-search me-2"></i>Тайна
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,23 +61,23 @@ function initPlaceFilters($filtersContainer) {
|
||||
<div class="col-md-6">
|
||||
<h5 class="mb-3">Бюджет</h5>
|
||||
<div class="d-flex flex-wrap mb-4">
|
||||
<div class="filter-btn category-budget" onclick="togglePriceFilter('BUDGET')">
|
||||
<div class="filter-btn category-budget" data-type="price" data-value="BUDGET">
|
||||
<i class="fas fa-wallet me-2"></i>Эконом (до 2,000₽)
|
||||
</div>
|
||||
<div class="filter-btn category-luxury" onclick="togglePriceFilter('LUXURY')">
|
||||
<div class="filter-btn category-luxury" data-type="price" data-value="LUXURY">
|
||||
<i class="fas fa-gem me-2"></i>Премиум (от 5,000₽)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mb-3">Время</h5>
|
||||
<div class="d-flex flex-wrap">
|
||||
<div class="filter-btn" onclick="toggleTimeFilter('DAY')">
|
||||
<div class="filter-btn" data-type="time" data-value="DAY">
|
||||
<i class="fas fa-sun me-2"></i>День
|
||||
</div>
|
||||
<div class="filter-btn" onclick="toggleTimeFilter('EVENING')">
|
||||
<div class="filter-btn" data-type="time" data-value="EVENING">
|
||||
<i class="fas fa-moon me-2"></i>Вечер
|
||||
</div>
|
||||
<div class="filter-btn" onclick="toggleTimeFilter('NIGHT')">
|
||||
<div class="filter-btn" data-type="time" data-value="NIGHT">
|
||||
<i class="fas fa-star me-2"></i>Ночь
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,5 +87,9 @@ function initPlaceFilters($filtersContainer) {
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
|
||||
// Вешаем один обработчик на все кнопки
|
||||
$filters.on('click', '.filter-btn', toggleFilter);
|
||||
|
||||
$filtersContainer.append($filters);
|
||||
}
|
||||
35
src/main/resources/static/js/site/blocks/places-block.js
Normal file
35
src/main/resources/static/js/site/blocks/places-block.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import {createCard} from "./single-place-card.js";
|
||||
|
||||
export function initPlacesBlock($container) {
|
||||
console.log("initPlacesBlock")
|
||||
$container.append(
|
||||
$(`
|
||||
<div class="container">
|
||||
<div class="row" id="placesContainer">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
)
|
||||
|
||||
function loadPlaces(filters = {}) {
|
||||
$('#placesContainer').empty().append('<div class="text-center">Загрузка...</div>');
|
||||
|
||||
// Задержка 500ms перед отрисовкой
|
||||
setTimeout(() => {
|
||||
$('#placesContainer').empty();
|
||||
console.log("новые фильтры в loadPlaces: ", filters)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
$('#placesContainer').append($(createCard()));
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
$(document).on('filtersChanged', (e, filters) => {
|
||||
console.log('Фильтры изменились:', filters);
|
||||
loadPlaces(filters);
|
||||
});
|
||||
|
||||
// первая загрузка
|
||||
loadPlaces()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export function createCard() {
|
||||
return `
|
||||
<div class="col-md-4">
|
||||
<div class="card place-card">
|
||||
<div class="position-relative">
|
||||
<img src="https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80" class="place-img" alt="Ресторан 'Панорама'">
|
||||
<div class="place-category romantic-badge">Романтическое</div>
|
||||
<div class="heart-counter">
|
||||
<i class="fas fa-heart"></i> 128
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ресторан 'Панорама'</h5>
|
||||
<p class="card-text text-muted">Ресторан на 25-м этаже с панорамным видом на город. Идеальное место для романтического ужина при све...</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="rating">
|
||||
★★★★½
|
||||
<small class="text-muted ms-1">4.8</small>
|
||||
</span>
|
||||
<div class="price-tag mt-1">4500₽</div>
|
||||
</div>
|
||||
<button class="btn btn-heart btn-sm" onclick="viewPlaceDetails(1)">
|
||||
<i class="fas fa-info-circle me-1"></i>Подробнее
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
6
src/main/resources/static/js/site/pages/home/home.js
Normal file
6
src/main/resources/static/js/site/pages/home/home.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import {initPlacesBlock} from "../../blocks/places-block.js";
|
||||
import {initPlaceFilters} from "../../blocks/place-filters.js";
|
||||
|
||||
|
||||
initPlaceFilters($('#mainPageContainer'));
|
||||
initPlacesBlock($('#mainPageContainer'));
|
||||
@@ -28,7 +28,4 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script src="/js/site/blocks/place-filters.js"></script>
|
||||
<script>
|
||||
initPlaceFilters($('#mainPageContainer'))
|
||||
</script>
|
||||
<script type="module" src="/js/site/pages/home/home.js"></script>
|
||||
Reference in New Issue
Block a user