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