File size: 14,354 Bytes
cf599e0
b866d20
4433399
b866d20
 
 
 
cf599e0
 
 
b866d20
 
 
6aad87c
 
b866d20
 
6aad87c
 
 
b866d20
 
 
cf599e0
 
b866d20
 
 
6aad87c
b866d20
 
 
 
 
 
 
 
6aad87c
 
 
 
 
 
 
b866d20
 
6aad87c
b866d20
 
 
6aad87c
b866d20
 
 
 
46cf1b2
b866d20
6aad87c
b866d20
 
6aad87c
b866d20
 
 
6aad87c
 
b866d20
 
 
 
 
6aad87c
 
b866d20
 
 
 
 
6aad87c
 
b866d20
 
6aad87c
b866d20
 
 
 
 
 
 
 
6aad87c
b866d20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6aad87c
b866d20
 
 
 
 
 
 
 
 
 
 
 
 
 
6aad87c
b866d20
 
 
 
 
 
 
 
 
 
 
 
 
 
6aad87c
b866d20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46cf1b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b866d20
 
 
 
 
 
 
46cf1b2
 
 
 
 
 
 
 
 
 
 
b866d20
 
 
 
 
 
 
 
 
 
 
46cf1b2
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
246
247
248
249
250
251
252
253
254
255
256
257
@page "/Borrowings/Manage"
@using BlazorFrontend.Services
@using LibraryManagement.Shared.Models
@using Microsoft.AspNetCore.Authorization
@inject LibraryApiClient ApiClient
@inject IJSRuntime JS
@attribute [Authorize(Roles = "Librarian")]

<PageTitle>Manage Borrowings</PageTitle>

<div class="mb-10 animate-in fade-in slide-in-from-top-4 duration-500">
    <div class="flex items-center justify-between mb-4">
        <div>
            <h1 class="text-3xl font-extrabold tracking-tight text-mystic-900 border-b border-mystic-100 pb-4 mb-2">Manage All Borrowings</h1>
            <p class="text-mystic-500 font-medium">Monitor and process library loans across all members.</p>
        </div>
        <div class="flex gap-3">
            <div class="card px-4 py-2 flex items-center gap-2 bg-mystic-50 border-mystic-200">
                <span class="text-xs font-bold text-mystic-400 uppercase tracking-widest">Total Active:</span>
                <span class="text-lg font-bold text-mystic-900">@_borrowings.Count(m => m.Status == "Borrowed")</span>
            </div>
        </div>
    </div>
</div>

@if (_isLoading)
{
    <div class="py-20 flex justify-center">
        <div class="animate-spin rounded-full h-10 w-10 border-b-2 border-mystic-900"></div>
    </div>
}
else
{
    <div class="card overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-700">
        <div class="overflow-x-auto">
            <table class="w-full text-left border-collapse">
                <thead>
                    <tr class="bg-mystic-50 border-b border-mystic-200">
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Book & Loan Info</th>
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Member Details</th>
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Loan Dates</th>
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest text-center">Status</th>
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Fines</th>
                        <th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest text-right">Actions</th>
                    </tr>
                </thead>
                <tbody class="divide-y divide-mystic-100">
                    @if (!_borrowings.Any())
                    {
                        <tr>
                            <td colspan="6" class="px-6 py-16 text-center text-mystic-400 italic bg-white">
                                No borrowing records found in the system.
                            </td>
                        </tr>
                    }
                    @foreach (var loan in PaginatedBorrowings)
                    {
                        <tr class="hover:bg-mystic-50/80 transition-colors">
                            <td class="px-6 py-5">
                                <div class="flex items-center gap-4">
                                    <div class="w-10 h-14 bg-mystic-900/5 rounded-lg flex items-center justify-center text-mystic-400 border border-mystic-100">
                                        <svg class="w-5 h-5" 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>
                                    <div class="min-w-0">
                                        <p class="text-sm font-bold text-mystic-900 truncate mb-0.5">@loan.BookTitle</p>
                                        <p class="text-[10px] font-mono text-mystic-400 uppercase tracking-tight">ID: #@loan.Id</p>
                                    </div>
                                </div>
                            </td>
                            <td class="px-6 py-5">
                                <div class="flex flex-col gap-1">
                                    <p class="text-sm font-semibold text-mystic-800">@loan.UserEmail</p>
                                    <p class="text-[10px] text-mystic-500 font-medium tracking-wide">UID: #@loan.UserId</p>
                                </div>
                            </td>
                            <td class="px-6 py-5">
                                <div class="grid gap-1">
                                    <div class="flex items-center gap-2 text-[11px]">
                                        <span class="text-mystic-400 w-12">Out:</span>
                                        <span class="font-bold text-mystic-700">@loan.BorrowDate.ToString("MMM dd, yyyy")</span>
                                    </div>
                                    <div class="flex items-center gap-2 text-[11px]">
                                        <span class="text-mystic-400 w-12">Due:</span>
                                        @{
                                            var isOverdue = loan.DueDate < DateTime.Now && loan.Status == "Borrowed";
                                        }
                                        <span class="font-bold @(isOverdue ? "text-rose-600 animate-pulse" : "text-amber-700")">@loan.DueDate.ToString("MMM dd, yyyy")</span>
                                    </div>
                                    @if (loan.ReturnDate.HasValue)
                                    {
                                        <div class="flex items-center gap-2 text-[11px]">
                                            <span class="text-mystic-400 w-12">Back:</span>
                                            <span class="font-bold text-emerald-600">@loan.ReturnDate.Value.ToString("MMM dd, yyyy")</span>
                                        </div>
                                    }
                                </div>
                            </td>
                            <td class="px-6 py-5 text-center">
                                <span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider
                                    @(loan.Status == "Borrowed" ? "bg-amber-100 text-amber-700" : 
                                      loan.Status == "PendingReturn" ? "bg-blue-600 text-white shadow-sm" :
                                      "bg-emerald-100 text-emerald-700")">
                                    @(loan.Status == "PendingReturn" ? "Pending Approval" : loan.Status)
                                </span>
                            </td>
                            <td class="px-6 py-5">
                                @if (loan.FineAmount > 0)
                                {
                                    <div class="flex flex-col">
                                        <span class="text-sm font-extrabold @(loan.IsFinePaid ? "text-mystic-400 line-through" : "text-rose-600")">
                                            MMK @loan.FineAmount.ToString("N0")
                                        </span>
                                        @if (loan.IsFinePaid)
                                        {
                                            <span class="text-[9px] text-emerald-600 font-bold uppercase mt-0.5 tracking-tighter">Settled</span>
                                        }
                                        else
                                        {
                                            <span class="text-[9px] text-rose-400 font-bold uppercase mt-0.5 tracking-tighter">Pending</span>
                                        }
                                    </div>
                                }
                                else
                                {
                                    <span class="text-[10px] text-mystic-300 font-bold italic">No Fines</span>
                                }
                            </td>
                            <td class="px-6 py-5 text-right">
                                @if (loan.Status == "PendingReturn")
                                {
                                    <button @onclick="() => ApproveReturnAsync(loan)" 
                                            class="inline-flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-emerald-700 transition-all shadow-md active:scale-95">
                                        <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="3" d="M5 13l4 4L19 7"></path></svg>
                                        Approve Return
                                    </button>
                                }
                                else if (loan.Status == "Borrowed")
                                {
                                    <button @onclick="() => ForceReturnAsync(loan)"
                                            class="inline-flex items-center gap-2 bg-mystic-900 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-mystic-800 transition-all shadow-sm active:scale-95">
                                        Process Return
                                    </button>
                                }
                                else
                                {
                                    <div class="text-emerald-600">
                                        <svg class="w-6 h-6 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
                                    </div>
                                }
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>

        @if (TotalPages > 1)
        {
            <div class="p-6 bg-mystic-900/5 border-t border-mystic-200 flex items-center justify-between">
                <p class="text-xs text-mystic-500 font-medium">
                    Showing <span class="font-bold text-mystic-900">@((_currentPage - 1) * _pageSize + 1)</span> to 
                    <span class="font-bold text-mystic-900">@(Math.Min(_currentPage * _pageSize, _borrowings.Count()))</span> of 
                    <span class="font-bold text-mystic-900">@_borrowings.Count()</span> records
                </p>
                <div class="flex items-center gap-2">
                    <button disabled="@(_currentPage == 1)" @onclick="() => _currentPage--" 
                            class="p-2 rounded-lg border border-mystic-200 text-mystic-600 hover:bg-white disabled:opacity-30 disabled:cursor-not-allowed transition-all">
                        <svg class="w-4 h-4" 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"></path></svg>
                    </button>
                    
                    <span class="text-xs font-bold text-mystic-900 px-2 tracking-tight">Page @_currentPage of @TotalPages</span>

                    <button disabled="@(_currentPage == TotalPages)" @onclick="() => _currentPage++" 
                            class="p-2 rounded-lg border border-mystic-200 text-mystic-600 hover:bg-white disabled:opacity-30 disabled:cursor-not-allowed transition-all">
                        <svg class="w-4 h-4" 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"></path></svg>
                    </button>
                </div>
            </div>
        }
    </div>
}

@code {
    private bool _isLoading = true;
    private IEnumerable<BorrowingDto> _borrowings = Enumerable.Empty<BorrowingDto>();

    // Pagination
    private int _currentPage = 1;
    private int _pageSize = 10;

    private IEnumerable<BorrowingDto> PaginatedBorrowings => _borrowings
        .OrderByDescending(l => l.BorrowDate)
        .Skip((_currentPage - 1) * _pageSize)
        .Take(_pageSize);

    private int TotalPages => (int)Math.Ceiling(_borrowings.Count() / (double)_pageSize);

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
    }

    private async Task LoadDataAsync()
    {
        _isLoading = true;
        try
        {
            _borrowings = await ApiClient.GetAllBorrowingsAsync();
            _currentPage = 1;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading all borrowings: {ex.Message}");
        }
        finally
        {
            _isLoading = false;
        }
    }

    private async Task ApproveReturnAsync(BorrowingDto loan)
    {
        bool confirmed = await JS.InvokeAsync<bool>("confirm", $"Approve return for \"{loan.BookTitle}\" borrowed by {loan.UserEmail}?");
        if (!confirmed) return;

        var result = await ApiClient.ReturnBookAsync(loan.Id);
        if (result != null)
        {
            await JS.InvokeVoidAsync("showToast", "Return approved successfully!", "success");
            await LoadDataAsync();
        }
        else
        {
            await JS.InvokeVoidAsync("showToast", "Failed to approve return.", "error");
        }
    }

    private async Task ForceReturnAsync(BorrowingDto loan)
    {
        bool confirmed = await JS.InvokeAsync<bool>("confirm", $"Force process return for \"{loan.BookTitle}\" (Member: {loan.UserEmail})? Use this if the member returned the book in person.");
        if (!confirmed) return;

        var result = await ApiClient.ReturnBookAsync(loan.Id);
        if (result != null)
        {
            await JS.InvokeVoidAsync("showToast", "Book marked as returned!", "success");
            await LoadDataAsync();
        }
        else
        {
            await JS.InvokeVoidAsync("showToast", "Failed to process return.", "error");
        }
    }
}