Spaces:
Sleeping
Sleeping
File size: 10,999 Bytes
6f32ae8 b0a0ed0 36144b0 b0a0ed0 1723ad3 b0a0ed0 861f0db b0a0ed0 861f0db 6f32ae8 861f0db b0a0ed0 36144b0 b0a0ed0 861f0db b0a0ed0 1723ad3 b0a0ed0 36144b0 861f0db 36144b0 861f0db 36144b0 861f0db 36144b0 6f32ae8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | @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>
}
|