@page "/"
@inject ServiceLivresApi Api
@implements IDisposable
MaBibli — catalogue
Catalogue
@if (_auteur is not null)
{
@* Le « cliquer sur un auteur » d'IDEES.md : le catalogue est ici restreint à ses livres. *@
Livres de @_auteur.Nom .
Revenir à toute la bibliothèque
}
FiltrerFormat(null)">Tous
@foreach (var format in Enum.GetValues())
{
FiltrerFormat(format)">@Libelles.Format(format)
}
@* Ce filtre porte sur MON statut, pas sur celui du foyer : un livre sans statut pour moi
ne remonte sous aucune valeur, il reste visible sous « Tous ». *@
FiltrerStatut(null)">Tous
@foreach (var statut in Enum.GetValues())
{
FiltrerStatut(statut)">@Libelles.Statut(statut)
}
@if (_erreur is not null)
{
@_erreur
}
@if (_chargement && _livres is null)
{
Chargement…
}
else if (_livres is { Count: 0 })
{
@(EstFiltre ? "Aucun livre ne correspond à cette recherche." : "La bibliothèque est vide. Ajoutez un premier livre.")
}
else if (_livres is not null)
{
@_livres.Count livre@(_livres.Count > 1 ? "s" : "")
@foreach (var livre in _livres)
{
@livre.Titre
@if (livre.Auteurs.Count > 0)
{
@foreach (var (auteur, index) in livre.Auteurs.Select((a, i) => (a, i)))
{
@(index > 0 ? ", " : "")
@auteur.Nom
}
}
@Libelles.Format(livre.Format)
@if (!string.IsNullOrWhiteSpace(livre.Editeur))
{
@livre.Editeur
}
@* Changement de statut : action la plus fréquente, donc directement dans la liste. *@
}
}
Ajouter par ISBN
Saisie manuelle
Auteurs
@code {
/// Restreint le catalogue à un auteur. Vient de « ?auteur=… », posé par les liens de la liste.
[SupplyParameterFromQuery(Name = "auteur")]
public int? AuteurId { get; set; }
private IReadOnlyList? _livres;
private AuteurDto? _auteur;
private int? _auteurCharge;
private string _recherche = string.Empty;
private Format? _format;
private Statut? _statut;
private bool _chargement;
private string? _erreur;
private int? _statutEnCours;
// Une frappe au clavier annule la recherche précédente encore en vol.
private CancellationTokenSource _cts = new();
private bool EstFiltre =>
!string.IsNullOrWhiteSpace(_recherche) || _format is not null || _statut is not null
|| AuteurId is not null;
protected override async Task OnParametersSetAsync()
{
if (_auteurCharge != AuteurId)
{
_auteurCharge = AuteurId;
_auteur = AuteurId is { } id ? await Api.ObtenirAuteurAsync(id) : null;
}
await ChargerAsync();
}
private async Task SurRecherche(ChangeEventArgs e)
{
_recherche = e.Value?.ToString() ?? string.Empty;
await ChargerAsync();
}
private Task FiltrerFormat(Format? format)
{
_format = format;
return ChargerAsync();
}
private Task FiltrerStatut(Statut? statut)
{
_statut = statut;
return ChargerAsync();
}
private async Task ChargerAsync()
{
_cts.Cancel();
_cts.Dispose();
_cts = new CancellationTokenSource();
var jeton = _cts.Token;
_chargement = true;
_erreur = null;
try
{
var criteres = new CritereLivres
{
Recherche = _recherche,
Format = _format,
Statut = _statut,
AuteurId = AuteurId,
};
_livres = await Api.ListerAsync(criteres, jeton);
}
catch (OperationCanceledException)
{
return; // Une recherche plus récente a pris le relais.
}
catch (Exception ex)
{
_erreur = $"Impossible de charger le catalogue : {ex.Message}";
}
finally
{
if (!jeton.IsCancellationRequested)
{
_chargement = false;
}
}
}
private async Task ChangerStatut(LivreDto livre, Statut? statut)
{
if (livre.Statut == statut)
{
return;
}
_statutEnCours = livre.Id;
var resultat = await Api.ChangerStatutAsync(livre.Id, statut);
_statutEnCours = null;
if (!resultat.EstOk)
{
_erreur = resultat.Erreur;
return;
}
if (_statut is not null)
{
// Un filtre par statut est actif : le livre vient peut-être de sortir de la sélection,
// il faut donc recharger plutôt que corriger la liste en place.
await ChargerAsync();
return;
}
// Remplacement local : évite de recharger toute la liste pour un seul champ.
_livres = _livres!.Select(l => l.Id == livre.Id ? resultat.Valeur! : l).ToList();
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}