Yuyuqt
add: books, borrowings, home, login, membership, register, reward, userinsights, wishlist, themes
6f32ae8
Raw
History Blame Contribute Delete
11 kB
@model Frontend.Models.PagedResult<LibraryManagement.Shared.Models.BookDto>
@{
ViewData["Title"] = "Books Catalog";
}
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
<div>
<h1 class="text-3xl font-bold tracking-tight text-slate-900">Books Catalog</h1>
<p class="mt-2 text-slate-500">
Explore and discover your next great read.
<span class="ml-2 text-xs text-slate-400">(@Model.TotalCount total)</span>
</p>
</div>
@if (User.IsInRole("Librarian"))
{
<div class="flex items-center gap-3">
<a asp-action="Create" class="btn-primary">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
Add New Book
</a>
</div>
}
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="md:col-span-3">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
</div>
<input type="text" class="block w-full pl-10 pr-3 py-2 border border-slate-200 rounded-lg bg-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent sm:text-sm shadow-sm" placeholder="Search by title, author or ISBN...">
</div>
</div>
</div>
@* Category Filter Pills *@
@{
var categories = ViewBag.Categories as IEnumerable<LibraryManagement.Shared.Models.CategoryDto> ?? Enumerable.Empty<LibraryManagement.Shared.Models.CategoryDto>();
int? selectedCategoryId = ViewBag.SelectedCategoryId;
}
@if (categories.Any())
{
<div class="flex flex-wrap items-center gap-2 mb-8">
<span class="text-xs font-semibold text-slate-400 uppercase tracking-wider mr-1">Filter:</span>
<a asp-action="Index" asp-route-page="1"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold border transition-colors @(selectedCategoryId == null ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-600 border-slate-200 hover:border-slate-400 hover:text-slate-900")">
All
</a>
@foreach (var cat in categories)
{
<a asp-action="Index" asp-route-page="1" asp-route-categoryId="@cat.Id"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold border transition-colors @(selectedCategoryId == cat.Id ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-600 border-slate-200 hover:border-slate-400 hover:text-slate-900")">
@cat.Name
</a>
}
</div>
}
<!-- Book Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-4 gap-8">
@foreach (var book in Model.Items)
{
<div class="group card hover:shadow-lg transition-all duration-300">
<div class="relative aspect-[2/3] w-full bg-slate-100 flex items-center justify-center text-slate-300 overflow-hidden text-center p-4">
<div class="flex flex-col items-center gap-4">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
<span class="text-[10px] font-bold text-slate-400">@book.Isbn</span>
</div>
@if (book.AvailableCopies <= 0)
{
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-[2px] flex items-center justify-center">
<span class="bg-white text-slate-900 text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded">Out of Stock</span>
</div>
}
</div>
<div class="p-4">
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<h3 class="text-sm font-bold text-slate-900 truncate group-hover:text-amber-600 transition-colors">
<a asp-action="Details" asp-route-id="@book.Id">@book.Title</a>
</h3>
<p class="text-xs text-slate-500 mt-0.5 truncate">@book.Author</p>
</div>
</div>
@if (book.Categories.Any())
{
<div class="mt-2 flex flex-wrap gap-1">
@foreach (var cat in book.Categories)
{
<a asp-action="Index" asp-route-page="1" asp-route-categoryId="@cat.Id"
class="inline-flex items-center rounded-full bg-amber-50 border border-amber-200 px-2 py-0.5 text-[9px] font-semibold text-amber-700 hover:bg-amber-100 transition-colors">
@cat.Name
</a>
}
</div>
}
<div class="mt-3 flex items-center justify-between">
<span class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-medium text-slate-600">@book.Status</span>
<span class="text-[10px] font-medium @(book.AvailableCopies > 0 ? "text-emerald-600" : "text-rose-600")">
@book.AvailableCopies / @book.TotalCopies
</span>
</div>
<div class="mt-4 pt-4 border-t border-slate-50 flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<a asp-action="Details" asp-route-id="@book.Id" class="flex-1 btn-secondary py-1 text-[11px]">Details</a>
@if (User.IsInRole("Librarian"))
{
<a asp-action="Edit" asp-route-id="@book.Id" class="p-1.5 rounded-md border border-slate-200 hover:bg-slate-50 text-slate-600">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
</a>
}
</div>
</div>
</div>
}
</div>
@* Pagination *@
@if (Model.TotalPages > 1)
{
<div class="mt-10 flex items-center justify-between">
<p class="text-sm text-slate-500">
Showing <span class="font-medium text-slate-700">@((Model.CurrentPage - 1) * Model.PageSize + 1)</span>
<span class="font-medium text-slate-700">@(Math.Min(Model.CurrentPage * Model.PageSize, Model.TotalCount))</span>
of <span class="font-medium text-slate-700">@Model.TotalCount</span> books
</p>
<nav class="flex items-center gap-1" aria-label="Pagination">
@* Previous *@
@if (Model.HasPreviousPage)
{
<a asp-action="Index" asp-route-page="@(Model.CurrentPage - 1)" asp-route-categoryId="@selectedCategoryId"
class="inline-flex items-center px-3 py-1.5 rounded-md border border-slate-200 text-sm font-medium text-slate-600 hover:bg-slate-50 hover:text-slate-900 transition-colors">
<svg class="w-4 h-4 mr-1" 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>
Prev
</a>
}
else
{
<span class="inline-flex items-center px-3 py-1.5 rounded-md border border-slate-100 text-sm font-medium text-slate-300 cursor-not-allowed select-none">
<svg class="w-4 h-4 mr-1" 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>
Prev
</span>
}
@* Page numbers *@
@for (int p = 1; p <= Model.TotalPages; p++)
{
if (p == Model.CurrentPage)
{
<span class="inline-flex items-center justify-center w-8 h-8 rounded-md bg-slate-900 text-white text-sm font-semibold">@p</span>
}
else if (p == 1 || p == Model.TotalPages || (p >= Model.CurrentPage - 2 && p <= Model.CurrentPage + 2))
{
<a asp-action="Index" asp-route-page="@p" asp-route-categoryId="@selectedCategoryId"
class="inline-flex items-center justify-center w-8 h-8 rounded-md border border-slate-200 text-sm font-medium text-slate-600 hover:bg-slate-50 hover:text-slate-900 transition-colors">@p</a>
}
else if (p == Model.CurrentPage - 3 || p == Model.CurrentPage + 3)
{
<span class="inline-flex items-center justify-center w-8 h-8 text-sm text-slate-400"></span>
}
}
@* Next *@
@if (Model.HasNextPage)
{
<a asp-action="Index" asp-route-page="@(Model.CurrentPage + 1)" asp-route-categoryId="@selectedCategoryId"
class="inline-flex items-center px-3 py-1.5 rounded-md border border-slate-200 text-sm font-medium text-slate-600 hover:bg-slate-50 hover:text-slate-900 transition-colors">
Next
<svg class="w-4 h-4 ml-1" 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>
</a>
}
else
{
<span class="inline-flex items-center px-3 py-1.5 rounded-md border border-slate-100 text-sm font-medium text-slate-300 cursor-not-allowed select-none">
Next
<svg class="w-4 h-4 ml-1" 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>
</span>
}
</nav>
</div>
}