mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 12:31:09 +00:00
Добавление функционала поиска и обновление документации
This commit is contained in:
+314
-27
@@ -1,6 +1,9 @@
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
let selectedAuthors = new Set();
|
let selectedAuthors = new Map(); // Map<id, name>
|
||||||
let selectedGenres = new Set();
|
let selectedGenres = new Map(); // Map<id, name>
|
||||||
|
let currentPage = 1;
|
||||||
|
let pageSize = 20;
|
||||||
|
let totalBooks = 0;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetch("/api/authors").then((response) => response.json()),
|
fetch("/api/authors").then((response) => response.json()),
|
||||||
@@ -11,7 +14,8 @@ $(document).ready(function () {
|
|||||||
authorsData.authors.forEach((author) => {
|
authorsData.authors.forEach((author) => {
|
||||||
$("<div>")
|
$("<div>")
|
||||||
.addClass("p-2 hover:bg-gray-100 cursor-pointer author-item")
|
.addClass("p-2 hover:bg-gray-100 cursor-pointer author-item")
|
||||||
.attr("data-value", author.name)
|
.attr("data-id", author.id)
|
||||||
|
.attr("data-name", author.name)
|
||||||
.text(author.name)
|
.text(author.name)
|
||||||
.appendTo($dropdown);
|
.appendTo($dropdown);
|
||||||
});
|
});
|
||||||
@@ -22,7 +26,7 @@ $(document).ready(function () {
|
|||||||
.addClass("mb-1")
|
.addClass("mb-1")
|
||||||
.html(
|
.html(
|
||||||
`<label class="custom-checkbox flex items-center">
|
`<label class="custom-checkbox flex items-center">
|
||||||
<input type="checkbox" data-genre="${genre.name}" />
|
<input type="checkbox" data-id="${genre.id}" data-name="${genre.name}" />
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
${genre.name}
|
${genre.name}
|
||||||
</label>`,
|
</label>`,
|
||||||
@@ -32,9 +36,271 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
initializeAuthorDropdown();
|
initializeAuthorDropdown();
|
||||||
initializeFilters();
|
initializeFilters();
|
||||||
|
|
||||||
|
// Загружаем книги при старте
|
||||||
|
loadBooks();
|
||||||
})
|
})
|
||||||
.catch((error) => console.error("Error loading data:", error));
|
.catch((error) => console.error("Error loading data:", error));
|
||||||
|
|
||||||
|
// === Функция загрузки книг ===
|
||||||
|
function loadBooks() {
|
||||||
|
const searchQuery = $("#book-search-input").val().trim();
|
||||||
|
|
||||||
|
// Формируем URL с параметрами
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
// Добавляем поиск (минимум 3 символа)
|
||||||
|
if (searchQuery.length >= 3) {
|
||||||
|
params.append("q", searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем авторов
|
||||||
|
selectedAuthors.forEach((name, id) => {
|
||||||
|
params.append("author_ids", id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем жанры
|
||||||
|
selectedGenres.forEach((name, id) => {
|
||||||
|
params.append("genre_ids", id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Пагинация
|
||||||
|
params.append("page", currentPage);
|
||||||
|
params.append("size", pageSize);
|
||||||
|
|
||||||
|
const url = `/api/books/filter?${params.toString()}`;
|
||||||
|
|
||||||
|
// Показываем индикатор загрузки
|
||||||
|
showLoadingState();
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
totalBooks = data.total;
|
||||||
|
renderBooks(data.books);
|
||||||
|
renderPagination();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error loading books:", error);
|
||||||
|
showErrorState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Отображение книг ===
|
||||||
|
function renderBooks(books) {
|
||||||
|
const $container = $("#books-container");
|
||||||
|
$container.empty();
|
||||||
|
|
||||||
|
if (books.length === 0) {
|
||||||
|
$container.html(`
|
||||||
|
<div class="bg-white p-8 rounded-lg shadow-md text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||||
|
</svg>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">Книги не найдены</h3>
|
||||||
|
<p class="text-gray-500">Попробуйте изменить параметры поиска или фильтры</p>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
books.forEach((book) => {
|
||||||
|
const authorsText =
|
||||||
|
book.authors.map((a) => a.name).join(", ") || "Автор неизвестен";
|
||||||
|
const genresText =
|
||||||
|
book.genres.map((g) => g.name).join(", ") || "Без жанра";
|
||||||
|
|
||||||
|
const $bookCard = $(`
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-md mb-4 hover:shadow-lg transition-shadow duration-200 cursor-pointer book-card" data-id="${book.id}">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-bold mb-1 text-gray-900 hover:text-blue-600 transition-colors">
|
||||||
|
${escapeHtml(book.title)}
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">
|
||||||
|
<span class="font-medium">Авторы:</span> ${escapeHtml(authorsText)}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-700 text-sm mb-2">
|
||||||
|
${escapeHtml(book.description || "Описание отсутствует")}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
${book.genres
|
||||||
|
.map(
|
||||||
|
(g) => `
|
||||||
|
<span class="inline-block bg-gray-100 text-gray-600 text-xs px-2 py-1 rounded-full">
|
||||||
|
${escapeHtml(g.name)}
|
||||||
|
</span>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
$container.append($bookCard);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик клика на карточку книги
|
||||||
|
$container.on("click", ".book-card", function () {
|
||||||
|
const bookId = $(this).data("id");
|
||||||
|
window.location.href = `/books/${bookId}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Пагинация ===
|
||||||
|
function renderPagination() {
|
||||||
|
// Удаляем старую пагинацию
|
||||||
|
$("#pagination-container").remove();
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalBooks / pageSize);
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
const $pagination = $(`
|
||||||
|
<div id="pagination-container" class="flex justify-center items-center gap-2 mt-6 mb-4">
|
||||||
|
<button id="prev-page" class="px-3 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" ${currentPage === 1 ? "disabled" : ""}>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div id="page-numbers" class="flex gap-1"></div>
|
||||||
|
<button id="next-page" class="px-3 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" ${currentPage === totalPages ? "disabled" : ""}>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const $pageNumbers = $pagination.find("#page-numbers");
|
||||||
|
|
||||||
|
// Генерируем номера страниц
|
||||||
|
const pages = generatePageNumbers(currentPage, totalPages);
|
||||||
|
|
||||||
|
pages.forEach((page) => {
|
||||||
|
if (page === "...") {
|
||||||
|
$pageNumbers.append(`<span class="px-3 py-2">...</span>`);
|
||||||
|
} else {
|
||||||
|
const isActive = page === currentPage;
|
||||||
|
$pageNumbers.append(`
|
||||||
|
<button class="page-btn px-3 py-2 rounded-lg ${isActive ? "bg-gray-500 text-white" : "bg-white border border-gray-300 hover:bg-gray-50"}" data-page="${page}">
|
||||||
|
${page}
|
||||||
|
</button>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#books-container").after($pagination);
|
||||||
|
|
||||||
|
// Обработчики пагинации
|
||||||
|
$("#prev-page").on("click", function () {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
loadBooks();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#next-page").on("click", function () {
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
loadBooks();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".page-btn").on("click", function () {
|
||||||
|
const page = parseInt($(this).data("page"));
|
||||||
|
if (page !== currentPage) {
|
||||||
|
currentPage = page;
|
||||||
|
loadBooks();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePageNumbers(current, total) {
|
||||||
|
const pages = [];
|
||||||
|
const delta = 2;
|
||||||
|
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
if (
|
||||||
|
i === 1 ||
|
||||||
|
i === total ||
|
||||||
|
(i >= current - delta && i <= current + delta)
|
||||||
|
) {
|
||||||
|
pages.push(i);
|
||||||
|
} else if (pages[pages.length - 1] !== "...") {
|
||||||
|
pages.push("...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
$("html, body").animate({ scrollTop: 0 }, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Состояния загрузки ===
|
||||||
|
function showLoadingState() {
|
||||||
|
const $container = $("#books-container");
|
||||||
|
$container.html(`
|
||||||
|
<div class="space-y-4">
|
||||||
|
${Array(3)
|
||||||
|
.fill()
|
||||||
|
.map(
|
||||||
|
() => `
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-md animate-pulse">
|
||||||
|
<div class="h-6 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-1/4 mb-2"></div>
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-full mb-2"></div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="h-6 bg-gray-200 rounded-full w-16"></div>
|
||||||
|
<div class="h-6 bg-gray-200 rounded-full w-20"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showErrorState() {
|
||||||
|
const $container = $("#books-container");
|
||||||
|
$container.html(`
|
||||||
|
<div class="bg-red-50 p-8 rounded-lg shadow-md text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-red-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
<h3 class="text-lg font-medium text-red-900 mb-2">Ошибка загрузки</h3>
|
||||||
|
<p class="text-red-700 mb-4">Не удалось загрузить список книг</p>
|
||||||
|
<button id="retry-btn" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">
|
||||||
|
Попробовать снова
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
$("#retry-btn").on("click", loadBooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Экранирование HTML ===
|
||||||
|
function escapeHtml(text) {
|
||||||
|
if (!text) return "";
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Dropdown авторов ===
|
||||||
function initializeAuthorDropdown() {
|
function initializeAuthorDropdown() {
|
||||||
const $input = $("#author-search-input");
|
const $input = $("#author-search-input");
|
||||||
const $dropdown = $("#author-dropdown");
|
const $dropdown = $("#author-dropdown");
|
||||||
@@ -42,8 +308,8 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
function updateHighlights() {
|
function updateHighlights() {
|
||||||
$dropdown.find(".author-item").each(function () {
|
$dropdown.find(".author-item").each(function () {
|
||||||
const value = $(this).attr("data-value");
|
const id = $(this).attr("data-id");
|
||||||
const isSelected = selectedAuthors.has(value);
|
const isSelected = selectedAuthors.has(parseInt(id));
|
||||||
$(this)
|
$(this)
|
||||||
.toggleClass("bg-gray-300 text-gray-600", isSelected)
|
.toggleClass("bg-gray-300 text-gray-600", isSelected)
|
||||||
.toggleClass("hover:bg-gray-100", !isSelected);
|
.toggleClass("hover:bg-gray-100", !isSelected);
|
||||||
@@ -59,10 +325,10 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
function renderChips() {
|
function renderChips() {
|
||||||
$container.find(".author-chip").remove();
|
$container.find(".author-chip").remove();
|
||||||
selectedAuthors.forEach((author) => {
|
selectedAuthors.forEach((name, id) => {
|
||||||
$(`<span class="author-chip flex items-center bg-gray-500 text-white text-sm font-medium px-2.5 py-0.5 rounded-full">
|
$(`<span class="author-chip flex items-center bg-gray-500 text-white text-sm font-medium px-2.5 py-0.5 rounded-full">
|
||||||
${author}
|
${escapeHtml(name)}
|
||||||
<button type="button" class="remove-author ml-1.5 inline-flex items-center p-0.5 text-gray-200 hover:text-white hover:bg-gray-600 rounded-full" data-author="${author}">
|
<button type="button" class="remove-author ml-1.5 inline-flex items-center p-0.5 text-gray-200 hover:text-white hover:bg-gray-600 rounded-full" data-id="${id}">
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 14 14">
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 14 14">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -72,11 +338,13 @@ $(document).ready(function () {
|
|||||||
updateHighlights();
|
updateHighlights();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAuthor(author) {
|
function toggleAuthor(id, name) {
|
||||||
if (selectedAuthors.has(author)) {
|
id = parseInt(id);
|
||||||
selectedAuthors.delete(author);
|
if (selectedAuthors.has(id)) {
|
||||||
|
selectedAuthors.delete(id);
|
||||||
} else {
|
} else {
|
||||||
selectedAuthors.add(author);
|
selectedAuthors.add(id, name);
|
||||||
|
selectedAuthors.set(id, name);
|
||||||
}
|
}
|
||||||
$input.val("");
|
$input.val("");
|
||||||
filterDropdown("");
|
filterDropdown("");
|
||||||
@@ -101,13 +369,13 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$dropdown.on("click", ".author-item", function (e) {
|
$dropdown.on("click", ".author-item", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleAuthor($(this).attr("data-value"));
|
toggleAuthor($(this).attr("data-id"), $(this).attr("data-name"));
|
||||||
$input.focus();
|
$input.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
$container.on("click", ".remove-author", function (e) {
|
$container.on("click", ".remove-author", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
selectedAuthors.delete($(this).attr("data-author"));
|
selectedAuthors.delete(parseInt($(this).attr("data-id")));
|
||||||
renderChips();
|
renderChips();
|
||||||
$input.focus();
|
$input.focus();
|
||||||
});
|
});
|
||||||
@@ -122,29 +390,30 @@ $(document).ready(function () {
|
|||||||
window.updateAuthorHighlights = updateHighlights;
|
window.updateAuthorHighlights = updateHighlights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Инициализация фильтров ===
|
||||||
function initializeFilters() {
|
function initializeFilters() {
|
||||||
const $bookSearch = $("#book-search-input");
|
const $bookSearch = $("#book-search-input");
|
||||||
const $applyBtn = $("#apply-filters-btn");
|
const $applyBtn = $("#apply-filters-btn");
|
||||||
const $resetBtn = $("#reset-filters-btn");
|
const $resetBtn = $("#reset-filters-btn");
|
||||||
|
|
||||||
|
// Обработка жанров
|
||||||
$("#genres-list").on("change", "input[type='checkbox']", function () {
|
$("#genres-list").on("change", "input[type='checkbox']", function () {
|
||||||
const genre = $(this).attr("data-genre");
|
const id = parseInt($(this).attr("data-id"));
|
||||||
|
const name = $(this).attr("data-name");
|
||||||
if ($(this).is(":checked")) {
|
if ($(this).is(":checked")) {
|
||||||
selectedGenres.add(genre);
|
selectedGenres.set(id, name);
|
||||||
} else {
|
} else {
|
||||||
selectedGenres.delete(genre);
|
selectedGenres.delete(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Применить фильтры
|
||||||
$applyBtn.on("click", function () {
|
$applyBtn.on("click", function () {
|
||||||
const searchQuery = $bookSearch.val().trim();
|
currentPage = 1; // Сбрасываем на первую страницу
|
||||||
console.log("Применены фильтры:", {
|
loadBooks();
|
||||||
search: searchQuery,
|
|
||||||
authors: Array.from(selectedAuthors),
|
|
||||||
genres: Array.from(selectedGenres),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Сбросить фильтры
|
||||||
$resetBtn.on("click", function () {
|
$resetBtn.on("click", function () {
|
||||||
$bookSearch.val("");
|
$bookSearch.val("");
|
||||||
|
|
||||||
@@ -155,18 +424,36 @@ $(document).ready(function () {
|
|||||||
selectedGenres.clear();
|
selectedGenres.clear();
|
||||||
$("#genres-list input[type='checkbox']").prop("checked", false);
|
$("#genres-list input[type='checkbox']").prop("checked", false);
|
||||||
|
|
||||||
console.log("Фильтры сброшены");
|
currentPage = 1;
|
||||||
|
loadBooks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Поиск с дебаунсом
|
||||||
let searchTimeout;
|
let searchTimeout;
|
||||||
$bookSearch.on("input", function () {
|
$bookSearch.on("input", function () {
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
|
||||||
|
// Автопоиск только если >= 3 символов или пусто
|
||||||
|
if (query.length >= 3 || query.length === 0) {
|
||||||
searchTimeout = setTimeout(() => {
|
searchTimeout = setTimeout(() => {
|
||||||
console.log("Поиск:", $(this).val());
|
currentPage = 1;
|
||||||
}, 300);
|
loadBooks();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Поиск по Enter
|
||||||
|
$bookSearch.on("keypress", function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
currentPage = 1;
|
||||||
|
loadBooks();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Остальной код (пользователь/авторизация) ===
|
||||||
const $guestLink = $("#guest-link");
|
const $guestLink = $("#guest-link");
|
||||||
const $userBtn = $("#user-btn");
|
const $userBtn = $("#user-btn");
|
||||||
const $userDropdown = $("#user-dropdown");
|
const $userDropdown = $("#user-dropdown");
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ content %}
|
|||||||
type="text"
|
type="text"
|
||||||
id="book-search-input"
|
id="book-search-input"
|
||||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-gray-500"
|
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-gray-500"
|
||||||
placeholder="Поиск книг..."
|
placeholder="Поиск книг (мин. 3 символа)..."
|
||||||
|
minlength="3"
|
||||||
|
maxlength="50"
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400"
|
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400"
|
||||||
@@ -34,26 +36,26 @@ content %}
|
|||||||
<h3 class="font-medium mb-2">Авторы</h3>
|
<h3 class="font-medium mb-2">Авторы</h3>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap gap-2 p-2 border border-gray-300 rounded-md bg-white"
|
class="flex flex-wrap gap-2 p-2 border border-gray-300 rounded-md bg-white min-h-[42px]"
|
||||||
id="selected-authors-container"
|
id="selected-authors-container"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="author-search-input"
|
id="author-search-input"
|
||||||
class="flex-grow outline-none bg-transparent"
|
class="flex-grow outline-none bg-transparent min-w-[100px]"
|
||||||
placeholder="Начните вводить..."
|
placeholder="Начните вводить..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="author-dropdown"
|
id="author-dropdown"
|
||||||
class="absolute z-10 w-full bg-white border border-gray-300 rounded-md mt-1 hidden max-h-60 overflow-y-auto"
|
class="absolute z-10 w-full bg-white border border-gray-300 rounded-md mt-1 hidden max-h-60 overflow-y-auto shadow-lg"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h3 class="font-medium mb-2">Жанры</h3>
|
<h3 class="font-medium mb-2">Жанры</h3>
|
||||||
<ul id="genres-list"></ul>
|
<ul id="genres-list" class="max-h-60 overflow-y-auto"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -68,46 +70,19 @@ content %}
|
|||||||
>
|
>
|
||||||
Сбросить фильтры
|
Сбросить фильтры
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Счётчик результатов -->
|
||||||
|
<div
|
||||||
|
id="results-counter"
|
||||||
|
class="mt-4 text-center text-sm text-gray-500"
|
||||||
|
></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="flex-1" id="books-container">
|
<main class="flex-1">
|
||||||
<div
|
<div id="books-container">
|
||||||
class="bg-white p-4 rounded-lg shadow-md mb-4 flex justify-between items-start"
|
<!-- Книги будут загружены через JS -->
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-bold mb-1">Product Title 1</h3>
|
|
||||||
<p class="text-gray-700 text-sm">
|
|
||||||
A short description of the product, highlighting its key
|
|
||||||
features and benefits.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="text-lg font-semibold text-gray-600">$29.99</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="bg-white p-4 rounded-lg shadow-md mb-4 flex justify-between items-start"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-bold mb-1">Product Title 2</h3>
|
|
||||||
<p class="text-gray-700 text-sm">
|
|
||||||
Another great product with amazing features. You'll love it!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="text-lg font-semibold text-blue-600">$49.99</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="bg-white p-4 rounded-lg shadow-md mb-4 flex justify-between items-start"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-bold mb-1">Product Title 3</h3>
|
|
||||||
<p class="text-gray-700 text-sm">
|
|
||||||
This product is a must-have for every modern home. High
|
|
||||||
quality and durable.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="text-lg font-semibold text-gray-600">$19.99</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Пагинация добавляется динамически после books-container -->
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|||||||
Reference in New Issue
Block a user