Spaces:
Sleeping
Sleeping
File size: 14,474 Bytes
cf599e0 0acba57 4433399 b866d20 cf599e0 b866d20 6aad87c cf599e0 b866d20 6aad87c cf599e0 b866d20 0acba57 b866d20 cf9e5bb b866d20 cf9e5bb b866d20 cf9e5bb b866d20 cf9e5bb b866d20 cf9e5bb b866d20 cf9e5bb b866d20 cf9e5bb b866d20 0acba57 b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 6aad87c b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 0acba57 b866d20 4433399 | 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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | @page "/"
@using BlazorFrontend.Services
@using LibraryManagement.Shared.Models
@using Microsoft.AspNetCore.Components.Authorization
@inject LibraryApiClient ApiClient
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Navigation
<PageTitle>Dashboard</PageTitle>
<div class="mb-10 animate-in fade-in slide-in-from-top-4 duration-500">
<h1 class="text-3xl font-extrabold tracking-tight text-mystic-900 border-b border-mystic-100 pb-4 mb-2">Welcome Back</h1>
<p class="text-mystic-500 font-medium">Here's what's happening with the library today.</p>
</div>
@if (_isLoading)
{
<div class="flex items-center justify-center min-h-[400px]">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-mystic-900"></div>
</div>
}
else
{
<AuthorizeView>
<Authorized>
<!-- Loyalty Banner -->
@if (_loyaltyAccount != null)
{
<a href="Rewards" class="block group mb-10 animate-in fade-in zoom-in-95 duration-700">
<div class="bg-gradient-to-br from-mystic-900 to-[#5a5669] rounded-3xl p-8 text-white shadow-xl relative overflow-hidden transition-all duration-500 hover:shadow-2xl hover:translate-y-[-4px] border border-white/5">
<div class="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<p class="text-mystic-200 text-xs font-bold uppercase tracking-[0.2em] mb-2 opacity-80">Your Loyalty Status</p>
<div class="flex items-baseline gap-3">
<h2 class="text-5xl font-black tracking-tighter">@_loyaltyAccount.CurrentBalance.ToString("N0")</h2>
<span class="text-mystic-300 font-bold tracking-wide text-lg">Points Earned</span>
</div>
<p class="text-[10px] text-white/40 mt-3 uppercase tracking-widest font-bold">Member Account: Verified</p>
</div>
<div class="flex items-center gap-6">
<div class="flex flex-col items-end">
<span class="text-[10px] text-mystic-300 font-bold uppercase tracking-widest mb-1 opacity-60">Current Tier</span>
<div class="inline-flex items-center gap-3 bg-white/10 backdrop-blur-md px-6 py-3 rounded-2xl border border-white/10 shadow-xl">
<div class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></div>
<span class="font-black text-xl tracking-tight">@_loyaltyAccount.Tier</span>
</div>
</div>
<div class="w-12 h-12 bg-white/10 rounded-2xl flex items-center justify-center group-hover:bg-white/20 transition-all duration-300">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 5l7 7-7 7"></path></svg>
</div>
</div>
</div>
<!-- Decorative Elements -->
<div class="absolute -right-20 -bottom-20 w-64 h-64 bg-white/5 rounded-full blur-3xl transform group-hover:scale-125 transition-transform duration-1000"></div>
<div class="absolute -left-20 -top-20 w-48 h-48 bg-mystic-100/5 rounded-full blur-2xl transform group-hover:scale-110 transition-transform duration-1000"></div>
</div>
</a>
}
</Authorized>
</AuthorizeView>
<!-- Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
<div class="card p-6 flex flex-col gap-3 group hover:border-mystic-300 transition-all duration-300">
<div class="flex items-center justify-between">
<div class="p-2 bg-mystic-50 rounded-lg group-hover:bg-mystic-100 transition-colors">
<svg class="w-5 h-5 text-mystic-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18 18.246 18.477 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
</div>
<span class="text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Books</span>
</div>
<div>
<div class="text-3xl font-bold text-mystic-900">@_totalBooks</div>
<div class="text-xs text-mystic-500 mt-1 font-medium italic">Total in catalog</div>
</div>
</div>
<div class="card p-6 flex flex-col gap-3 group hover:border-mystic-300 transition-all duration-300">
<div class="flex items-center justify-between">
<div class="p-2 bg-emerald-50 rounded-lg group-hover:bg-emerald-100 transition-colors">
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path></svg>
</div>
<span class="text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Active Loans</span>
</div>
<div>
<div class="text-3xl font-bold text-mystic-900">@_activeLoans</div>
<div class="text-xs text-mystic-500 mt-1 font-medium italic">Checking out right now</div>
</div>
</div>
<div class="card p-6 flex flex-col gap-3 group hover:border-mystic-300 border-l-4 border-l-rose-500 transition-all duration-300">
<div class="flex items-center justify-between">
<div class="p-2 bg-rose-50 rounded-lg group-hover:bg-rose-100 transition-colors">
<svg class="w-5 h-5 text-rose-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
<span class="text-[10px] font-bold text-mystic-400 uppercase tracking-widest text-rose-500">Overdue</span>
</div>
<div>
<div class="text-3xl font-bold text-rose-600">@_overdueLoans</div>
<div class="text-xs text-rose-500 mt-1 font-medium italic">Require attention</div>
</div>
</div>
<div class="card p-6 flex flex-col gap-3 group hover:border-mystic-300 transition-all duration-300">
<div class="flex items-center justify-between">
<div class="p-2 bg-blue-50 rounded-lg group-hover:bg-blue-100 transition-colors">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
</div>
<span class="text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Active Members</span>
</div>
<div>
<div class="text-3xl font-bold text-mystic-900">@_totalMembers</div>
<div class="text-xs text-mystic-500 mt-1 font-medium italic">In the community</div>
</div>
</div>
</div>
<!-- Recent Arrivals -->
@if (_recentBooks.Any())
{
<div class="mb-12 animate-in fade-in slide-in-from-bottom-6 duration-700 delay-150">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold text-mystic-900 tracking-tight">Recent Arrivals</h2>
<a href="Books" class="text-sm font-bold text-mystic-500 hover:text-mystic-900 flex items-center group">
Browse All
<svg class="w-4 h-4 ml-1 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
</a>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
@foreach (var book in _recentBooks)
{
<div class="group cursor-pointer">
<a href="Books">
<div class="aspect-[2/3] w-full bg-mystic-100 rounded-xl mb-3 flex items-center justify-center text-mystic-300 group-hover:shadow-lg transition-all duration-300 relative overflow-hidden">
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18 18.246 18.477 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
@if (book.AvailableCopies <= 0)
{
<div class="absolute inset-0 bg-mystic-900/40 backdrop-blur-[1px] flex items-center justify-center">
<span class="bg-white text-mystic-900 text-[8px] font-bold uppercase px-2 py-1 rounded">Out</span>
</div>
}
</div>
<h3 class="text-xs font-bold text-mystic-900 truncate group-hover:text-mystic-600 transition-colors">@book.Title</h3>
<p class="text-[10px] text-mystic-500 truncate mt-0.5 font-medium">@book.Author</p>
</a>
</div>
}
</div>
</div>
}
<!-- Quick Actions -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 animate-in fade-in slide-in-from-bottom-8 duration-700 delay-300">
<div class="bg-mystic-900 text-white rounded-3xl p-8 flex flex-col justify-between overflow-hidden relative group">
<div class="relative z-10">
<h2 class="text-2xl font-bold mb-2">Explore the Library</h2>
<p class="text-mystic-400 text-sm max-w-[280px]">Access thousands of books across all categories with a premium member account.</p>
</div>
<div class="mt-8 relative z-10">
<a href="Books" class="inline-flex items-center bg-white text-mystic-900 px-6 py-2.5 rounded-full text-sm font-bold hover:bg-mystic-100 transition-colors">
View Catalog
</a>
</div>
<svg class="absolute bottom-0 right-0 w-48 h-48 text-mystic-800 -mb-12 -mr-12 opacity-50 transition-transform group-hover:scale-110" fill="currentColor" viewBox="0 0 24 24"><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>
</div>
<div class="bg-mystic-50 rounded-3xl p-8 flex flex-col justify-between group border border-mystic-100">
<div>
<h2 class="text-2xl font-bold text-mystic-900 mb-2">My Current Loans</h2>
<p class="text-mystic-600 text-sm max-w-[280px]">Don't forget to return your books on time to avoid late fees.</p>
</div>
<div class="mt-8">
<a href="Borrowings" class="inline-flex items-center text-mystic-900 px-6 py-2.5 rounded-full text-sm font-bold border-2 border-mystic-900 hover:bg-mystic-900 hover:text-white transition-all">
Manage My Loans
</a>
</div>
</div>
</div>
}
@code {
private int _totalBooks = 0;
private int _activeLoans = 0;
private int _overdueLoans = 0;
private int _totalMembers = 0;
private IEnumerable<BookDto> _recentBooks = Enumerable.Empty<BookDto>();
private LoyaltyAccountDto? _loyaltyAccount;
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
try
{
// 1. Basic Stats (Public)
var books = await ApiClient.GetBooksAsync();
_totalBooks = books.Count();
_recentBooks = books.Take(6);
// 2. Personal/Authenticated Stats
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity?.IsAuthenticated == true)
{
// Fetch borrowings
try
{
var borrowings = await ApiClient.GetMyBorrowingsAsync();
_activeLoans = borrowings.Count(b => b.Status == "Borrowed");
_overdueLoans = borrowings.Count(b => b.Status == "Overdue" || (b.Status == "Borrowed" && b.DueDate < DateTime.UtcNow));
}
catch { }
// Fetch loyalty
try
{
_loyaltyAccount = await ApiClient.GetMyLoyaltyAccountAsync();
}
catch { }
// 3. Librarian Only Stats
if (user.IsInRole("Librarian"))
{
try
{
var members = await ApiClient.GetUsersAsync();
_totalMembers = members.Count();
}
catch { }
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error loading dashboard data: " + ex.Message);
}
finally
{
_isLoading = false;
}
}
}
|