Files
mabibli/MaBibli.Client/Pages/Prets.razor
T
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:36:16 +02:00

157 lines
5.3 KiB
Plaintext

@page "/prets"
@using MaBibli.Shared.Textes
@inject ServiceLivresApi Api
@inject EtatReseau Reseau
@implements IDisposable
@*
Ce qui n'est pas à la maison. C'est l'usage principal des prêts : savoir d'un coup d'œil
quels livres sont dehors, et chez qui.
La liste est commune à tout le foyer, sans filtre par utilisateur : n'importe qui doit
pouvoir constater qu'un livre manque, et noter son retour quand il le récupère.
*@
<PageTitle>MaBibli — prêts en cours</PageTitle>
<h1 class="titre-page">Prêts en cours</h1>
@if (_erreur is not null)
{
<p class="message-erreur" role="alert">@_erreur</p>
}
@if (_prets is null)
{
<p class="message-discret">Chargement…</p>
}
else if (_prets.Count == 0)
{
@* Hors-ligne, une liste vide peut aussi vouloir dire « rien n'a encore été enregistré ici » :
on ne l'affirme comme un fait que si l'information vient bien d'une synchronisation. *@
<p class="message-discret">
@(Reseau.EnLigne || Reseau.DerniereSynchro is not null
? "Tous les livres sont à la maison."
: "Aucune donnée enregistrée sur cet appareil : impossible de dire ce qui est prêté.")
</p>
}
else
{
<p class="compteur">@_prets.Count livre@(_prets.Count > 1 ? "s" : "") dehors</p>
@* Filtre en mémoire : la liste des prêts en cours est courte, une forme normalisée en
base serait disproportionnée pour ce seul besoin (voir CLAUDE.md, lot G). La recherche
reste insensible aux accents et à la casse, comme partout ailleurs dans l'application. *@
@if (_prets.Count > 1)
{
<div class="barre-recherche">
<input class="champ-saisie" type="search" placeholder="Filtrer par emprunteur…"
value="@_recherche" @oninput="SurRecherche" aria-label="Filtrer par emprunteur" />
</div>
}
@if (PretsAffiches().Count == 0)
{
<p class="message-discret">Aucun prêt ne correspond à « @_recherche ».</p>
}
<ul class="liste-livres">
@foreach (var pret in PretsAffiches())
{
<li class="carte-livre" @key="pret.Id">
<Couverture Url="@pret.LivreCoverUrl" Titre="@pret.LivreTitre"
Classe="couverture-petite" Agrandissable="true" />
<div class="carte-corps">
<a class="carte-titre" href="@($"livres/{pret.LivreId}")">@pret.LivreTitre</a>
@if (!string.IsNullOrWhiteSpace(pret.LivreAuteurs))
{
<p class="carte-auteur">@pret.LivreAuteurs</p>
}
<p class="carte-details">
<span class="etiquette etiquette-prete">@pret.Emprunteur</span>
<span>depuis le @pret.DatePret.ToLocalTime().ToString("dd/MM/yyyy")</span>
<span class="pret-duree">@PretsLivre.Duree(pret.JoursEcoules)</span>
</p>
@* Le retour se note ici : on récupère un livre, on ouvre la liste des prêts,
on coche. Passer par la fiche du livre serait un détour. *@
<button type="button" class="bouton bouton-discret bouton-compact"
disabled="@(_enCours == pret.Id || !Reseau.EnLigne)"
title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
@onclick="() => RendreAsync(pret)">
@(_enCours == pret.Id ? "Enregistrement…" : "Rendu")
</button>
</div>
</li>
}
</ul>
}
@code {
private IReadOnlyList<PretDto>? _prets;
private string? _erreur;
private int? _enCours;
private string _recherche = string.Empty;
private void SurRecherche(ChangeEventArgs e) => _recherche = e.Value?.ToString() ?? string.Empty;
private IReadOnlyList<PretDto> PretsAffiches()
{
if (_prets is null)
{
return [];
}
var terme = NormalisationTexte.Normaliser(_recherche);
return terme.Length == 0
? _prets
: _prets.Where(p => NormalisationTexte.Normaliser(p.Emprunteur).Contains(terme)).ToList();
}
protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
private void SurChangementReseau() => _ = InvokeAsync(async () =>
{
await ChargerAsync();
StateHasChanged();
});
public void Dispose() => Reseau.Change -= SurChangementReseau;
protected override Task OnInitializedAsync() => ChargerAsync();
private async Task ChargerAsync()
{
_erreur = null;
try
{
_prets = await Api.ListerPretsEnCoursAsync();
}
catch (Exception ex)
{
_erreur = $"Impossible de charger les prêts : {ex.Message}";
_prets = [];
}
}
private async Task RendreAsync(PretDto pret)
{
_enCours = pret.Id;
var resultat = await Api.RendreAsync(pret.Id);
_enCours = null;
if (!resultat.EstOk)
{
_erreur = resultat.Erreur;
}
// Rechargement complet plutôt que retrait local : quelqu'un d'autre du foyer a pu rendre
// ou prêter un livre entre-temps, et cette vue est justement celle qu'on garde ouverte.
await ChargerAsync();
}
}