Обновление фронтэнда

This commit is contained in:
2025-12-19 08:39:02 +03:00
parent 756e941f99
commit 16a043843a
6 changed files with 110 additions and 121 deletions
+5 -5
View File
@@ -11,9 +11,9 @@ from .misc import router as misc_router
api_router = APIRouter() api_router = APIRouter()
# Подключение всех маршрутов # Подключение всех маршрутов
api_router.include_router(auth_router)
api_router.include_router(authors_router)
api_router.include_router(books_router)
api_router.include_router(genres_router)
api_router.include_router(relationships_router)
api_router.include_router(misc_router) api_router.include_router(misc_router)
api_router.include_router(auth_router, prefix="/api")
api_router.include_router(authors_router, prefix="/api")
api_router.include_router(books_router, prefix="/api")
api_router.include_router(genres_router, prefix="/api")
api_router.include_router(relationships_router, prefix="/api")
+7
View File
@@ -34,6 +34,13 @@ async def root(request: Request, app=Depends(get_app)):
return templates.TemplateResponse(request, "index.html", get_info(app)) return templates.TemplateResponse(request, "index.html", get_info(app))
@router.get("/auth", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)):
"""Эндпоинт страницы авторизации"""
return templates.TemplateResponse(request, "auth.html", get_info(app))
@router.get("/api", include_in_schema=False) @router.get("/api", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)): async def root(request: Request, app=Depends(get_app)):
"""Страница с сылками на документацию API""" """Страница с сылками на документацию API"""
+54 -74
View File
@@ -1,72 +1,59 @@
// Load authors and genres asynchronously $(document).ready(function () {
Promise.all([ Promise.all([
fetch("/authors").then((response) => response.json()), fetch("/authors").then((response) => response.json()),
fetch("/genres").then((response) => response.json()), fetch("/genres").then((response) => response.json()),
]) ])
.then(([authorsData, genresData]) => { .then(([authorsData, genresData]) => {
// Populate authors dropdown const $dropdown = $("#author-dropdown");
const dropdown = document.getElementById("author-dropdown");
authorsData.authors.forEach((author) => { authorsData.authors.forEach((author) => {
const div = document.createElement("div"); const $div = $("<div>", {
div.className = "p-2 hover:bg-gray-100 cursor-pointer"; class: "p-2 hover:bg-gray-100 cursor-pointer",
div.setAttribute("data-value", author.name); "data-value": author.name,
div.textContent = author.name; text: author.name,
dropdown.appendChild(div); });
$dropdown.append($div);
}); });
// Populate genres list const $list = $("#genres-list");
const list = document.getElementById("genres-list");
genresData.genres.forEach((genre) => { genresData.genres.forEach((genre) => {
const li = document.createElement("li"); const $li = $("<li>", { class: "mb-1" });
li.className = "mb-1"; $li.html(`
li.innerHTML = `
<label class="custom-checkbox flex items-center"> <label class="custom-checkbox flex items-center">
<input type="checkbox" /> <input type="checkbox" />
<span class="checkmark"></span> <span class="checkmark"></span>
${genre.name} ${genre.name}
</label> </label>
`; `);
list.appendChild(li); $list.append($li);
}); });
initializeAuthorDropdown(); initializeAuthorDropdown();
}) })
.catch((error) => console.error("Error loading data:", error)); .catch((error) => console.error("Error loading data:", error));
function initializeAuthorDropdown() { function initializeAuthorDropdown() {
const authorSearchInput = document.getElementById("author-search-input"); const $authorSearchInput = $("#author-search-input");
const authorDropdown = document.getElementById("author-dropdown"); const $authorDropdown = $("#author-dropdown");
const selectedAuthorsContainer = document.getElementById( const $selectedAuthorsContainer = $("#selected-authors-container");
"selected-authors-container", const $dropdownItems = $authorDropdown.find("[data-value]");
);
const dropdownItems = authorDropdown.querySelectorAll("[data-value]");
let selectedAuthors = new Set(); let selectedAuthors = new Set();
// Function to update highlights in dropdown
const updateDropdownHighlights = () => { const updateDropdownHighlights = () => {
dropdownItems.forEach((item) => { $dropdownItems.each(function () {
const value = item.dataset.value; const value = $(this).data("value");
if (selectedAuthors.has(value)) { $(this).toggleClass("bg-gray-200", selectedAuthors.has(value));
item.classList.add("bg-gray-200");
} else {
item.classList.remove("bg-gray-200");
}
}); });
}; };
// Function to render selected authors
const renderSelectedAuthors = () => { const renderSelectedAuthors = () => {
Array.from(selectedAuthorsContainer.children).forEach((child) => { $selectedAuthorsContainer.children().not("#author-search-input").remove();
if (child.id !== "author-search-input") {
child.remove();
}
});
selectedAuthors.forEach((author) => { selectedAuthors.forEach((author) => {
const authorChip = document.createElement("span"); const $authorChip = $("<span>", {
authorChip.className = class:
"flex items-center bg-gray-200 text-gray-800 text-sm font-medium px-2.5 py-0.5 rounded-full"; "flex items-center bg-gray-200 text-gray-800 text-sm font-medium px-2.5 py-0.5 rounded-full",
authorChip.innerHTML = ` });
$authorChip.html(`
${author} ${author}
<button type="button" class="ml-1 inline-flex items-center p-0.5 text-sm text-gray-400 bg-transparent rounded-sm hover:bg-gray-200 hover:text-gray-900" data-author="${author}"> <button type="button" class="ml-1 inline-flex items-center p-0.5 text-sm text-gray-400 bg-transparent rounded-sm hover:bg-gray-200 hover:text-gray-900" data-author="${author}">
<svg class="w-2 h-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"> <svg class="w-2 h-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
@@ -74,62 +61,55 @@ function initializeAuthorDropdown() {
</svg> </svg>
<span class="sr-only">Remove author</span> <span class="sr-only">Remove author</span>
</button> </button>
`; `);
selectedAuthorsContainer.insertBefore(authorChip, authorSearchInput); $selectedAuthorsContainer.append($authorChip);
}); });
updateDropdownHighlights(); updateDropdownHighlights();
}; };
// Handle input focus to show dropdown $authorSearchInput.on("focus", () => {
authorSearchInput.addEventListener("focus", () => { $authorDropdown.removeClass("hidden");
authorDropdown.classList.remove("hidden");
}); });
// Handle input for filtering $authorSearchInput.on("input", function () {
authorSearchInput.addEventListener("input", () => { const query = $(this).val().toLowerCase();
const query = authorSearchInput.value.toLowerCase(); $dropdownItems.each(function () {
dropdownItems.forEach((item) => { const text = $(this).text().toLowerCase();
const text = item.textContent.toLowerCase(); $(this).css("display", text.includes(query) ? "block" : "none");
item.style.display = text.includes(query) ? "block" : "none";
}); });
authorDropdown.classList.remove("hidden"); $authorDropdown.removeClass("hidden");
}); });
// Handle clicks outside to hide dropdown $(document).on("click", function (event) {
document.addEventListener("click", (event) => {
if ( if (
!selectedAuthorsContainer.contains(event.target) && !$selectedAuthorsContainer.is(event.target) &&
!authorDropdown.contains(event.target) !$selectedAuthorsContainer.has(event.target).length &&
!$authorDropdown.is(event.target) &&
!$authorDropdown.has(event.target).length
) { ) {
authorDropdown.classList.add("hidden"); $authorDropdown.addClass("hidden");
} }
}); });
// Handle author selection from dropdown $authorDropdown.on("click", "[data-value]", function () {
authorDropdown.addEventListener("click", (event) => { const selectedValue = $(this).data("value");
const selectedValue = event.target.dataset.value;
if (selectedValue) {
if (selectedAuthors.has(selectedValue)) { if (selectedAuthors.has(selectedValue)) {
selectedAuthors.delete(selectedValue); selectedAuthors.delete(selectedValue);
} else { } else {
selectedAuthors.add(selectedValue); selectedAuthors.add(selectedValue);
} }
authorSearchInput.value = ""; $authorSearchInput.val("");
renderSelectedAuthors(); renderSelectedAuthors();
authorSearchInput.focus(); $authorSearchInput.focus();
}
}); });
// Handle removing selected author chip $selectedAuthorsContainer.on("click", "button", function () {
selectedAuthorsContainer.addEventListener("click", (event) => { const authorToRemove = $(this).data("author");
if (event.target.closest("button")) {
const authorToRemove = event.target.closest("button").dataset.author;
selectedAuthors.delete(authorToRemove); selectedAuthors.delete(authorToRemove);
renderSelectedAuthors(); renderSelectedAuthors();
authorSearchInput.focus(); $authorSearchInput.focus();
}
}); });
// Initial render and highlights (without auto-focus)
renderSelectedAuthors(); renderSelectedAuthors();
} }
});
+3 -2
View File
@@ -50,11 +50,12 @@
<p>Current Time: {{ server_time }}</p> <p>Current Time: {{ server_time }}</p>
<p>Status: {{ status }}</p> <p>Status: {{ status }}</p>
<ul> <ul>
<li><a href="/docs">Swagger UI</a></li> <li><a href="/">Home page</a></li>
<li><a href="/redoc">ReDoc</a></li>
<li> <li>
<a href="https://github.com/wowlikon/LibraryAPI">Source Code</a> <a href="https://github.com/wowlikon/LibraryAPI">Source Code</a>
</li> </li>
<li><a href="/docs">Swagger UI</a></li>
<li><a href="/redoc">ReDoc</a></li>
</ul> </ul>
</body> </body>
</html> </html>
View File
+3 -2
View File
@@ -1,9 +1,10 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>LiB</title>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LiB</title> <script src="https://unpkg.com/cash-dom@1.4.0/dist/cash.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="static/styles.css" /> <link rel="stylesheet" href="static/styles.css" />
</head> </head>
@@ -21,7 +22,7 @@
<a href="/" class="hover:text-gray-200">Home</a> <a href="/" class="hover:text-gray-200">Home</a>
</li> </li>
<li> <li>
<a href="#" class="hover:text-gray-200">Products</a> <a href="#" class="hover:text-gray-200">books</a>
</li> </li>
<li> <li>
<a href="#" class="hover:text-gray-200">About</a> <a href="#" class="hover:text-gray-200">About</a>