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>
}