добавил блок с местами, теперь блоки с фильтрами и местами независимы

This commit is contained in:
Lobstervova
2026-02-04 23:29:42 +03:00
parent d7a0cbb7a4
commit d86b640209
10 changed files with 194 additions and 59 deletions

View File

@@ -97,7 +97,7 @@ public class SecurityConfig {
.authorizeExchange(exchange -> exchange .authorizeExchange(exchange -> exchange
.anyExchange().permitAll() .anyExchange().permitAll()
) )
.csrf(ServerHttpSecurity.CsrfSpec::disable) // .csrf(ServerHttpSecurity.CsrfSpec::disable)
.build(); .build();
} }

View File

@@ -19,16 +19,5 @@ import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/account") @RequestMapping("/account")
public class AccountController { 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()
);
}
} }

View 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; }

View File

@@ -1,3 +1,4 @@
@import "fonts.css"; @import "fonts.css";
@import "main.css"; @import "main.css";
@import "header.css"; @import "header.css";
@import "places.css";

View File

@@ -103,6 +103,7 @@ function initHeader($header){
<!-- Форма входа --> <!-- Форма входа -->
<div class="tab-pane fade show active" id="login" role="tabpanel"> <div class="tab-pane fade show active" id="login" role="tabpanel">
<form id="loginForm" method="POST" action="/account/login"> <form id="loginForm" method="POST" action="/account/login">
<input type="hidden" name="${csrf.parameterName}" value="${csrf.token}" />
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Email или телефон</label> <label class="form-label">Email или телефон</label>
<input type="text" class="form-control" id="loginEmail" placeholder="example@mail.ru" required> <input type="text" class="form-control" id="loginEmail" placeholder="example@mail.ru" required>

View File

@@ -1,37 +1,35 @@
function toggleCategoryFilter(filter) { let currentFilters = {category: [], price: [], time: []};
const btn = event.target.closest('.filter-btn'); export function initPlaceFilters($filtersContainer) {
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) {
console.log("init places filters"); 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"> <section class="py-5">
<div class="container"> <div class="container">
<div class="filter-card"> <div class="filter-card">
@@ -40,22 +38,22 @@ function initPlaceFilters($filtersContainer) {
<div class="col-md-6"> <div class="col-md-6">
<h5 class="mb-3">Тип свидания</h5> <h5 class="mb-3">Тип свидания</h5>
<div class="d-flex flex-wrap mb-4"> <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>Романтическое <i class="fas fa-heart me-2"></i>Романтическое
</div> </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>Активное <i class="fas fa-hiking me-2"></i>Активное
</div> </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>Интеллектуальное <i class="fas fa-brain me-2"></i>Интеллектуальное
</div> </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>Хоррор <i class="fas fa-ghost me-2"></i>Хоррор
</div> </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>Уютное <i class="fas fa-mug-hot me-2"></i>Уютное
</div> </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>Тайна <i class="fas fa-search me-2"></i>Тайна
</div> </div>
</div> </div>
@@ -63,23 +61,23 @@ function initPlaceFilters($filtersContainer) {
<div class="col-md-6"> <div class="col-md-6">
<h5 class="mb-3">Бюджет</h5> <h5 class="mb-3">Бюджет</h5>
<div class="d-flex flex-wrap mb-4"> <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₽) <i class="fas fa-wallet me-2"></i>Эконом (до 2,000₽)
</div> </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₽) <i class="fas fa-gem me-2"></i>Премиум (от 5,000₽)
</div> </div>
</div> </div>
<h5 class="mb-3">Время</h5> <h5 class="mb-3">Время</h5>
<div class="d-flex flex-wrap"> <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>День <i class="fas fa-sun me-2"></i>День
</div> </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>Вечер <i class="fas fa-moon me-2"></i>Вечер
</div> </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>Ночь <i class="fas fa-star me-2"></i>Ночь
</div> </div>
</div> </div>
@@ -89,5 +87,9 @@ function initPlaceFilters($filtersContainer) {
</div> </div>
</section> </section>
`); `);
// Вешаем один обработчик на все кнопки
$filters.on('click', '.filter-btn', toggleFilter);
$filtersContainer.append($filters); $filtersContainer.append($filters);
} }

View 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()
}

View File

@@ -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&amp;auto=format&amp;fit=crop&amp;w=800&amp;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>
`
}

View File

@@ -0,0 +1,6 @@
import {initPlacesBlock} from "../../blocks/places-block.js";
import {initPlaceFilters} from "../../blocks/place-filters.js";
initPlaceFilters($('#mainPageContainer'));
initPlacesBlock($('#mainPageContainer'));

View File

@@ -28,7 +28,4 @@
</div> </div>
</section> </section>
</div> </div>
<script src="/js/site/blocks/place-filters.js"></script> <script type="module" src="/js/site/pages/home/home.js"></script>
<script>
initPlaceFilters($('#mainPageContainer'))
</script>