$(document).ready(() => { const STATUS_CONFIG = { active: { label: "Доступна", bgClass: "bg-green-100", textClass: "text-green-800", icon: ``, }, borrowed: { label: "Выдана", bgClass: "bg-yellow-100", textClass: "text-yellow-800", icon: ``, }, reserved: { label: "Забронирована", bgClass: "bg-blue-100", textClass: "text-blue-800", icon: ``, }, restoration: { label: "На реставрации", bgClass: "bg-orange-100", textClass: "text-orange-800", icon: ``, }, written_off: { label: "Списана", bgClass: "bg-red-100", textClass: "text-red-800", icon: ``, }, }; const pathParts = window.location.pathname.split("/"); const bookId = parseInt(pathParts[pathParts.length - 1]); let currentBook = null; let cachedUsers = null; let selectedLoanUserId = null; let activeLoan = null; init(); function init() { if (!bookId || isNaN(bookId)) { Utils.showToast("Некорректный ID книги", "error"); return; } loadBookData(); setupEventHandlers(); } function setupEventHandlers() { $(document).on("click", (e) => { const $menu = $("#status-menu"); const $toggleBtn = $("#status-toggle-btn"); if ( $menu.length && !$menu.hasClass("hidden") && !$toggleBtn.is(e.target) && $toggleBtn.has(e.target).length === 0 && !$menu.has(e.target).length ) { $menu.addClass("hidden"); } }); $("#cancel-loan-btn").on("click", closeLoanModal); $("#user-search-input").on("input", handleUserSearch); $("#confirm-loan-btn").on("click", submitLoan); $("#refresh-loans-btn").on("click", loadLoans); const future = new Date(); future.setDate(future.getDate() + 14); $("#loan-due-date").val(future.toISOString().split("T")[0]); } function loadBookData() { Api.get(`/api/books/${bookId}`) .then((book) => { currentBook = book; document.title = `LiB - ${book.title}`; renderBook(book); if (window.canManage()) { $("#edit-book-btn") .attr("href", `/book/${book.id}/edit`) .removeClass("hidden"); $("#loans-section").removeClass("hidden"); loadLoans(); } }) .catch((error) => { console.error(error); Utils.showToast("Книга не найдена", "error"); $("#book-loader").html( '

Ошибка загрузки

', ); }); } async function loadLoans() { if (!window.canManage()) return; try { const data = await Api.get( `/api/loans/?book_id=${bookId}&active_only=true&page=1&size=10` ); activeLoan = data.loans.length > 0 ? data.loans[0] : null; renderLoans(data.loans); } catch (error) { console.error("Failed to load loans", error); $("#loans-container").html( '
Ошибка загрузки выдач
', ); } } function renderLoans(loans) { const $container = $("#loans-container"); $container.empty(); if (!loans || loans.length === 0) { $container.html( '
Нет активных выдач
', ); return; } loans.forEach((loan) => { const borrowedDate = new Date(loan.borrowed_at).toLocaleDateString( "ru-RU" ); const dueDate = new Date(loan.due_date).toLocaleDateString("ru-RU"); const isOverdue = !loan.returned_at && new Date(loan.due_date) < new Date(); const $loanCard = $(`
ID выдачи: ${loan.id} ${ isOverdue ? 'Просрочена' : "" }

Дата выдачи: ${borrowedDate}

Срок возврата: ${dueDate}

Пользователь ID: ${loan.user_id}

${ !loan.returned_at && currentBook.status === "reserved" ? `` : "" } ${ !loan.returned_at ? `` : "" }
`); $loanCard.find(".confirm-loan-btn").on("click", function () { const loanId = $(this).data("loan-id"); confirmLoan(loanId); }); $loanCard.find(".return-loan-btn").on("click", function () { const loanId = $(this).data("loan-id"); returnLoan(loanId); }); $container.append($loanCard); }); } async function confirmLoan(loanId) { try { await Api.post(`/api/loans/${loanId}/confirm`); Utils.showToast("Бронь подтверждена", "success"); loadBookData(); loadLoans(); } catch (error) { console.error(error); Utils.showToast(error.message || "Ошибка подтверждения брони", "error"); } } async function returnLoan(loanId) { if (!confirm("Вы уверены, что хотите вернуть эту книгу?")) { return; } try { await Api.post(`/api/loans/${loanId}/return`); Utils.showToast("Книга возвращена", "success"); loadBookData(); loadLoans(); } catch (error) { console.error(error); Utils.showToast(error.message || "Ошибка возврата книги", "error"); } } function getStatusConfig(status) { return ( STATUS_CONFIG[status] || { label: status || "Неизвестно", bgClass: "bg-gray-100", textClass: "text-gray-800", icon: "", } ); } function renderBook(book) { $("#book-title").text(book.title); $("#book-id").text(`ID: ${book.id}`); $("#book-authors-text").text( book.authors.map((a) => a.name).join(", ") || "Автор неизвестен", ); $("#book-description").text(book.description || "Описание отсутствует"); renderStatusWidget(book); if (!window.canManage() && book.status === "active") { renderReserveButton(); } else { $("#book-actions-container").empty(); } if (book.genres && book.genres.length > 0) { $("#genres-section").removeClass("hidden"); const $genres = $("#genres-container"); $genres.empty(); book.genres.forEach((g) => { $genres.append(` ${Utils.escapeHtml(g.name)} `); }); } if (book.authors && book.authors.length > 0) { $("#authors-section").removeClass("hidden"); const $authors = $("#authors-container"); $authors.empty(); book.authors.forEach((a) => { $authors.append(`
${a.name.charAt(0).toUpperCase()}
${Utils.escapeHtml(a.name)}
`); }); } $("#book-loader").addClass("hidden"); $("#book-content").removeClass("hidden"); } function renderStatusWidget(book) { const $container = $("#book-status-container"); $container.empty(); const config = getStatusConfig(book.status); if (window.canManage()) { const $dropdownHTML = $(`
`); $container.append($dropdownHTML); $("#status-toggle-btn").on("click", (e) => { e.stopPropagation(); $("#status-menu").toggleClass("hidden"); }); $(".status-option").on("click", function () { const newStatus = $(this).data("status"); $("#status-menu").addClass("hidden"); if (newStatus === currentBook.status) return; if (newStatus === "borrowed") { openLoanModal(); } else { updateBookStatus(newStatus); } }); } else { $container.append(` ${config.icon} ${config.label} `); } } function renderReserveButton() { const $container = $("#book-actions-container"); $container.html(` `); $("#reserve-btn").on("click", function () { const user = window.getUser(); if (!user) { Utils.showToast("Необходима авторизация", "error"); return; } Api.post("/api/loans/", { book_id: currentBook.id, user_id: user.id, due_date: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(), }) .then((loan) => { Utils.showToast("Книга забронирована", "success"); loadBookData(); }) .catch((err) => { Utils.showToast(err.message || "Ошибка бронирования", "error"); }); }); } async function updateBookStatus(newStatus) { const $toggleBtn = $("#status-toggle-btn"); const originalContent = $toggleBtn.html(); $toggleBtn.prop("disabled", true).addClass("opacity-75").html(` Обновление... `); try { const payload = { status: newStatus, }; const updatedBook = await Api.put( `/api/books/${currentBook.id}`, payload, ); currentBook = updatedBook; Utils.showToast("Статус успешно изменен", "success"); renderStatusWidget(updatedBook); loadLoans(); } catch (error) { console.error(error); Utils.showToast(error.message || "Ошибка при смене статуса", "error"); $toggleBtn .prop("disabled", false) .removeClass("opacity-75") .html(originalContent); } } function openLoanModal() { $("#loan-modal").removeClass("hidden"); $("#user-search-input").val("")[0].focus(); $("#users-list-container").html( '
Загрузка списка пользователей...
', ); $("#confirm-loan-btn").prop("disabled", true); selectedLoanUserId = null; fetchUsers(); } function closeLoanModal() { $("#loan-modal").addClass("hidden"); } async function fetchUsers() { if (cachedUsers) { renderUsersList(cachedUsers); return; } try { const data = await Api.get("/api/auth/users?skip=0&limit=500"); cachedUsers = data.users; renderUsersList(cachedUsers); } catch (error) { console.error("Failed to load users", error); $("#users-list-container").html( '
Ошибка загрузки пользователей
', ); } } function renderUsersList(users) { const $container = $("#users-list-container"); $container.empty(); if (!users || users.length === 0) { $container.html( '
Пользователи не найдены
', ); return; } users.forEach((user) => { const roleBadges = user.roles .map((r) => { const color = r === "admin" ? "bg-purple-100 text-purple-800" : r === "librarian" ? "bg-blue-100 text-blue-800" : "bg-gray-100 text-gray-800"; return `${r}`; }) .join(""); const $item = $(`
${Utils.escapeHtml(user.full_name || user.username)}
@${Utils.escapeHtml(user.username)} • ${Utils.escapeHtml(user.email)}
${roleBadges}
`); $item.on("click", function () { $(".user-item").removeClass("bg-blue-100 border-l-4 border-blue-500"); $(this).addClass("bg-blue-100 border-l-4 border-blue-500"); selectedLoanUserId = user.id; $("#confirm-loan-btn") .prop("disabled", false) .text(`Выдать для ${user.username}`); }); $container.append($item); }); } function handleUserSearch() { const query = $(this).val().toLowerCase(); if (!cachedUsers) return; if (!query) { renderUsersList(cachedUsers); return; } const filtered = cachedUsers.filter( (u) => u.username.toLowerCase().includes(query) || (u.full_name && u.full_name.toLowerCase().includes(query)) || u.email.toLowerCase().includes(query), ); renderUsersList(filtered); } async function submitLoan() { if (!selectedLoanUserId) return; const dueDate = $("#loan-due-date").val(); if (!dueDate) { Utils.showToast("Выберите дату возврата", "error"); return; } const $btn = $("#confirm-loan-btn"); const originalText = $btn.text(); $btn.prop("disabled", true).text("Обработка..."); try { const payload = { book_id: currentBook.id, user_id: selectedLoanUserId, due_date: new Date(dueDate).toISOString(), }; // Используем прямой эндпоинт выдачи для администраторов if (window.isAdmin()) { await Api.post("/api/loans/issue", payload); } else { // Для библиотекарей создаем бронь, которую потом нужно подтвердить await Api.post("/api/loans/", payload); } Utils.showToast("Книга успешно выдана", "success"); closeLoanModal(); loadBookData(); loadLoans(); } catch (error) { console.error(error); Utils.showToast(error.message || "Ошибка выдачи", "error"); } finally { $btn.prop("disabled", false).text(originalText); } } });