Transforme la bibliographie en écran de travail D1 à D5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
LIMONIER Mathieu
2026-08-20 13:42:46 +02:00
co-authored by Copilot
parent ddc60898da
commit 359f4f59c6
13 changed files with 1044 additions and 20 deletions
+151 -15
View File
@@ -86,6 +86,10 @@ else if (_bibliographie is { } biblio)
<p class="compteur">
@biblio.Oeuvres.Count œuvre@(biblio.Oeuvres.Count > 1 ? "s" : "")
— @_possedees déjà chez vous
@if (_masquees > 0)
{
<text> — @_masquees masquée@(_masquees > 1 ? "s" : "")</text>
}
</p>
@* Deux phrases distinctes plutôt qu'une seule à trous : enchaîner des fragments
@@ -124,16 +128,37 @@ else if (_bibliographie is { } biblio)
<p class="message-erreur" role="alert">@_message</p>
}
@* Filtre sur une seule question : « qu'est-ce qui me manque ? ». Les livres possédés ne
sont jamais masqués par défaut — le grisage est l'information principale de l'écran. *@
<div class="filtres">
<div class="segments" role="group" aria-label="Filtrer la bibliographie">
<button type="button" class="segment @(_seulementManquants ? null : "segment-actif")"
@onclick="() => _seulementManquants = false">Tout</button>
<button type="button" class="segment @(_seulementManquants ? "segment-actif" : null)"
@onclick="() => _seulementManquants = true">Ce qui me manque</button>
</div>
<div class="actions-formulaire">
<button type="button" class="bouton bouton-discret"
@onclick="() => _filtresOuverts = !_filtresOuverts"
aria-expanded="@_filtresOuverts">
Filtrer (@NombreFiltres)
</button>
</div>
@if (_filtresOuverts)
{
<div class="filtres">
<div class="segments" role="group" aria-label="Possession">
<button type="button" class="segment @(_modePossession == 0 ? "segment-actif" : null)"
@onclick="() => _modePossession = 0">Tout</button>
<button type="button" class="segment @(_modePossession == 1 ? "segment-actif" : null)"
@onclick="() => _modePossession = 1">Ceux que j'ai déjà</button>
<button type="button" class="segment @(_modePossession == 2 ? "segment-actif" : null)"
@onclick="() => _modePossession = 2">Ce qui me manque</button>
</div>
@if (_masquees > 0)
{
<div class="segments">
<button type="button" class="segment @(_afficherMasquees ? "segment-actif" : null)"
@onclick="() => _afficherMasquees = true">
Afficher les masquées (@_masquees)
</button>
<button type="button" class="segment @(!_afficherMasquees ? "segment-actif" : null)"
@onclick="() => _afficherMasquees = false">Masquer les masquées</button>
</div>
}
</div>
}
<ul class="liste-bibliographie">
@foreach (var oeuvre in Affichees(biblio))
@@ -204,16 +229,51 @@ else if (_bibliographie is { } biblio)
@if (oeuvre.ADecouvrir)
{
<button type="button" class="bouton bouton-compact bouton-principal"
disabled="@_enCours" @onclick="() => SouhaiterAsync(oeuvre)"
aria-label="@($"Ajouter « {oeuvre.Titre} » à ma liste d'envies")">
Je le veux
<div class="actions-formulaire">
<label class="selection-oeuvre">
<input type="checkbox"
checked="@_selection.Contains(oeuvre.Titre)"
@onchange="e => Selectionner(oeuvre.Titre, e.Value is true)" />
Sélectionner
</label>
<button type="button" class="bouton bouton-compact bouton-principal"
disabled="@_enCours" @onclick="() => SouhaiterAsync(oeuvre)"
aria-label="@($"Ajouter « {oeuvre.Titre} » à ma liste d'envies")">
Je le veux
</button>
</div>
}
@if (!oeuvre.Masquee)
{
<button type="button" class="bouton bouton-compact bouton-discret"
disabled="@_enCours" @onclick="() => MasquerAsync(oeuvre)">
Masquer
</button>
}
else
{
<button type="button" class="bouton bouton-compact bouton-discret"
disabled="@_enCours" @onclick="() => DemasquerAsync(oeuvre)">
Réafficher
</button>
}
</li>
}
</ul>
@if (_selection.Count > 0)
{
<div class="actions-flottantes">
<button type="button" class="bouton bouton-principal"
disabled="@(_enCours || !Reseau.EnLigne)"
title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
@onclick="AjouterSelectionAsync">
Ajouter la sélection aux envies (@_selection.Count)
</button>
</div>
}
@* Le rapprochement est fait par titre, et il rate des choses (voir CleOeuvre). Le dire
franchement vaut mieux que laisser l'utilisateur découvrir seul qu'un livre qu'il
possède n'est pas grisé. *@
@@ -239,7 +299,9 @@ else if (_bibliographie is { } biblio)
public int AuteurId { get; set; }
private BibliographieDto? _bibliographie;
private bool _seulementManquants;
private int _modePossession;
private bool _filtresOuverts;
private bool _afficherMasquees;
private bool _enCours;
private bool _chargement;
@@ -250,13 +312,22 @@ else if (_bibliographie is { } biblio)
private string? _message;
private int _possedees;
private int _masquees;
private IReadOnlyList<LivreDto> _livresCatalogue = [];
private readonly HashSet<string> _oeuvresOuvertes = [];
private readonly HashSet<string> _selection = [];
protected override async Task OnParametersSetAsync() => await ChargerAsync();
private IEnumerable<OeuvreBibliographie> Affichees(BibliographieDto biblio) =>
_seulementManquants ? biblio.Oeuvres.Where(o => o.ADecouvrir) : biblio.Oeuvres;
biblio.Oeuvres.Where(o =>
(_afficherMasquees || !o.Masquee)
&& (_modePossession == 0
|| (_modePossession == 1 && o.Possede)
|| (_modePossession == 2 && o.ADecouvrir)));
private int NombreFiltres =>
(_modePossession == 0 ? 0 : 1) + (_masquees > 0 && !_afficherMasquees ? 1 : 0);
private async Task ChargerAsync()
{
@@ -278,6 +349,7 @@ else if (_bibliographie is { } biblio)
}
_possedees = _bibliographie.Oeuvres.Count(o => o.Possede);
_masquees = _bibliographie.Oeuvres.Count(o => o.Masquee);
}
catch (Exception)
{
@@ -300,6 +372,70 @@ else if (_bibliographie is { } biblio)
}
}
private void Selectionner(string titre, bool selectionnee)
{
if (selectionnee)
{
_selection.Add(titre);
}
else
{
_selection.Remove(titre);
}
}
private async Task AjouterSelectionAsync()
{
if (_bibliographie is null)
{
return;
}
var saisies = _bibliographie.Oeuvres
.Where(o => _selection.Contains(o.Titre) && o.ADecouvrir)
.Select(o => new EnregistrementSouhait
{
Titre = o.Titre,
Auteur = _bibliographie.Auteur.Nom,
Editeur = o.Editeur,
Annee = o.Annee,
Isbn = o.Isbn,
})
.ToList();
_enCours = true;
_message = null;
var resultats = await Api.AjouterSouhaitsAsync(saisies);
_enCours = false;
var erreurs = resultats.Where(r => !r.EstOk).Select(r => r.Erreur).Where(e => e is not null).ToList();
_message = erreurs.Count == 0 ? null : string.Join(" ", erreurs);
_selection.Clear();
await ChargerAsync();
}
private async Task MasquerAsync(OeuvreBibliographie oeuvre)
{
await ModifierMasquageAsync(oeuvre, false);
}
private async Task DemasquerAsync(OeuvreBibliographie oeuvre)
{
await ModifierMasquageAsync(oeuvre, true);
}
private async Task ModifierMasquageAsync(OeuvreBibliographie oeuvre, bool demasquer)
{
_enCours = true;
_message = demasquer
? await Api.DemasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre)
: await Api.MasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre);
_enCours = false;
if (_message is null)
{
await ChargerAsync();
}
}
private void BasculerOeuvre(string titre)
{
if (!_oeuvresOuvertes.Add(titre))