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>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
@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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user