Yuyuqt
add: blazor web assembly.
4433399
Raw
History Blame Contribute Delete
10.8 kB
@page "/Borrowings"
@using BlazorFrontend.Services
@using LibraryManagement.Shared.Models
@inject LibraryApiClient ApiClient
@inject IJSRuntime JS
<PageTitle>My Loans</PageTitle>
<div class="mb-8 animate-in fade-in slide-in-from-top-4 duration-500">
<h1 class="text-3xl font-bold tracking-tight text-mystic-900">My Loans</h1>
<p class="mt-2 text-mystic-500">Track your current borrowings and return upcoming books.</p>
</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/50 border-b border-mystic-100">
<th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Book Information</th>
<th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">Dates</th>
<th class="px-6 py-4 text-[10px] font-bold text-mystic-400 uppercase tracking-widest">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="5" class="px-6 py-12 text-center text-mystic-400 italic">
You don't have any borrowing history yet.
</td>
</tr>
}
@foreach (var loan in PaginatedBorrowings)
{
<tr class="hover:bg-mystic-50 transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="w-10 h-14 bg-mystic-100 rounded flex items-center justify-center text-mystic-300 group-hover:bg-mystic-200 transition-colors">
<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">@loan.BookTitle</p>
<p class="text-[11px] text-mystic-500 mt-0.5">Loan ID: #@loan.Id</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="space-y-1">
<p class="text-[11px]"><span class="text-mystic-400">Borrowed:</span> <span class="font-medium text-mystic-700">@loan.BorrowDate.ToString("MMM dd, yyyy")</span></p>
<p class="text-[11px]"><span class="text-mystic-400">Due:</span> <span class="font-medium @(loan.DueDate < DateTime.Now && loan.Status == "Borrowed" ? "text-rose-600" : "text-mystic-700")">@loan.DueDate.ToString("MMM dd, yyyy")</span></p>
@if (loan.ReturnDate.HasValue)
{
<p class="text-[11px]"><span class="text-mystic-400">Returned:</span> <span class="font-medium text-emerald-600">@loan.ReturnDate.Value.ToString("MMM dd, yyyy")</span></p>
}
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@(loan.Status == "Borrowed" ? "bg-amber-100 text-amber-700" :
loan.Status == "PendingReturn" ? "bg-blue-100 text-blue-700" :
"bg-emerald-100 text-emerald-700")">
@(loan.Status == "PendingReturn" ? "Pending Approval" : loan.Status)
</span>
</td>
<td class="px-6 py-4">
@if (loan.FineAmount > 0)
{
<div class="flex flex-col">
<span class="text-xs font-bold @(loan.IsFinePaid ? "text-mystic-500 line-through" : "text-rose-600")">
MMK @loan.FineAmount.ToString("N0")
</span>
@if (loan.IsFinePaid)
{
<span class="text-[10px] text-emerald-600 font-medium">Paid</span>
}
</div>
}
else
{
<span class="text-[11px] text-mystic-400">No Fines</span>
}
</td>
<td class="px-6 py-4 text-right">
@if (loan.Status == "Borrowed")
{
<button @onclick="() => RequestReturnAsync(loan)"
class="bg-mystic-900 text-white py-1.5 px-4 rounded-xl text-xs font-bold hover:bg-mystic-800 transition-all active:scale-95 shadow-sm">
Return Book
</button>
}
else if (loan.Status == "PendingReturn")
{
<span class="text-xs font-medium text-mystic-400 italic">Awaiting Approval</span>
}
else
{
<svg class="w-5 h-5 ml-auto text-emerald-500" 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>
}
</td>
</tr>
}
</tbody>
</table>
</div>
@if (TotalPages > 1)
{
<div class="p-6 bg-mystic-50/30 border-t border-mystic-100 flex items-center justify-between">
<p class="text-xs text-mystic-500">
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">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
.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.GetMyBorrowingsAsync();
_currentPage = 1;
}
catch (Exception ex)
{
Console.WriteLine($"Error loading borrowings: {ex.Message}");
}
finally
{
_isLoading = false;
}
}
private async Task RequestReturnAsync(BorrowingDto loan)
{
bool confirmed = await JS.InvokeAsync<bool>("confirm", $"Request return for \"{loan.BookTitle}\"? It will be marked as pending until approved by a librarian.");
if (!confirmed) return;
var success = await ApiClient.RequestReturnAsync(loan.Id);
if (success)
{
await JS.InvokeVoidAsync("showToast", "Return request submitted successfully!", "success");
await LoadDataAsync();
}
else
{
await JS.InvokeVoidAsync("showToast", "Failed to submit return request.", "error");
}
}
}