136 lines
5.8 KiB
Plaintext
136 lines
5.8 KiB
Plaintext
@using MaBibli.Client.Services
|
|
|
|
@*
|
|
Un bloc de série au catalogue, replié ou déplié — et récursif, parce qu'un cycle est une
|
|
série de séries : chaque niveau porte sa propre bascule.
|
|
|
|
⚠️ Tout est REPLIÉ par défaut ici (2026-09-10), à l'inverse de l'arbre des séries : le
|
|
catalogue liste D'ABORD les livres, une série n'y est qu'un détail qu'on ouvre au besoin —
|
|
alors que l'écran des séries EST la liste des séries, où les masquer toutes viderait l'écran.
|
|
|
|
⚠️ Replié, le bloc annonce toujours ce qu'il contient : sans ce compte, le compteur du haut
|
|
du catalogue paraîtrait mentir.
|
|
|
|
⚠️ Et ce compte distingue trois choses (2026-09-08) : ce qui est AFFICHÉ (filtre compris), ce
|
|
qu'on POSSÈDE de cette série, et ce qu'elle compte de tomes. Un seul nombre suivait le
|
|
filtre — « 3 tomes » devenait « 1 tome » sous une recherche, et se lisait « cette série n'a
|
|
qu'un tome ». Les égalités se replient : sans filtre et série complète, on relit « 3 tomes ».
|
|
*@
|
|
<li class="groupe-serie" @key="Entree.Serie!.Id">
|
|
<div class="groupe-serie-entete">
|
|
@* ⚠️ Aria.Etat, et non le booléen nu : voir la mise en garde qu'il porte. *@
|
|
<button type="button" class="groupe-serie-plier"
|
|
aria-expanded="@Aria.Etat(Deplie)"
|
|
aria-label="@($"{(Deplie ? "Replier" : "Déplier")} « {Entree.Serie!.Titre} »")"
|
|
@onclick="Basculer">
|
|
<span aria-hidden="true">@(Deplie ? "▾" : "▸")</span>
|
|
</button>
|
|
|
|
@* Le nom mène à la SÉRIE : c'est le seul écran qui montre aussi les tomes manquants,
|
|
ce que le catalogue ne peut pas faire. *@
|
|
<a class="groupe-serie-titre" href="@($"series/{Entree.Serie!.Id}")">
|
|
<span class="groupe-serie-nom">@Entree.Serie!.Titre</span>
|
|
<span class="groupe-serie-compte">
|
|
@Libelles.CompteBloc(Entree.NombreLivres, Possedes, Total)
|
|
</span>
|
|
</a>
|
|
</div>
|
|
|
|
@if (Deplie)
|
|
{
|
|
@if (Entree.SousEntrees.Count > 0)
|
|
{
|
|
<ul class="liste-livres">
|
|
@foreach (var sous in Entree.SousEntrees)
|
|
{
|
|
<GroupeCatalogue Entree="sous" Ouverts="Ouverts" Totaux="Totaux"
|
|
LotActif="LotActif" SelectionLot="SelectionLot" OnSelectionner="OnSelectionner" />
|
|
}
|
|
</ul>
|
|
}
|
|
|
|
@if (Entree.Livres.Count > 0)
|
|
{
|
|
<ul class="liste-livres">
|
|
@foreach (var livre in Entree.Livres)
|
|
{
|
|
@if (LotActif)
|
|
{
|
|
<li class="carte-oeuvre" @key="livre.Id">
|
|
<label class="oeuvre-case">
|
|
<input type="checkbox"
|
|
checked="@(SelectionLot?.Contains(livre.Id) ?? false)"
|
|
@onchange="e => OnSelectionner.InvokeAsync((livre.Id, e.Value is true))" />
|
|
<span class="lecture-ecran">Sélectionner « @livre.Titre »</span>
|
|
</label>
|
|
<CarteLivre Livre="livre" />
|
|
</li>
|
|
}
|
|
else
|
|
{
|
|
<li @key="livre.Id">
|
|
<CarteLivre Livre="livre" />
|
|
</li>
|
|
}
|
|
}
|
|
</ul>
|
|
}
|
|
}
|
|
</li>
|
|
|
|
@code {
|
|
[Parameter, EditorRequired] public EntreeCatalogue Entree { get; set; } = default!;
|
|
|
|
/// <summary>
|
|
/// Blocs OUVERTS, par identifiant de série — et non repliés : le défaut est de tout replier.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// État local de l'écran, volontairement non persistant : c'est un confort de lecture, pas
|
|
/// une propriété de la série. Il survit en revanche aux rechargements du catalogue (frappe
|
|
/// dans la recherche, filtre), le jeu appartenant à l'écran et non au bloc.
|
|
/// </remarks>
|
|
[Parameter, EditorRequired] public ISet<int> Ouverts { get; set; } = new HashSet<int>();
|
|
|
|
/// <summary>
|
|
/// Compteurs par série, descendance comprise : ce qu'on possède, et ce que la série recense.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// ⚠️ Calculés une fois par l'écran (<c>TotauxSeries</c>), et non ici : le catalogue se relit
|
|
/// à chaque frappe dans la recherche, et chaque bloc les recalculerait pour tout l'arbre.
|
|
/// C'est aussi ce qui garantit le <b>même</b> compte que la fiche de la série.
|
|
/// </remarks>
|
|
[Parameter] public IReadOnlyDictionary<int, (int Possedes, int Total)>? Totaux { get; set; }
|
|
|
|
/// <summary>Modification en lot du catalogue : voir la remarque équivalente sur Catalogue.razor.</summary>
|
|
[Parameter] public bool LotActif { get; set; }
|
|
|
|
[Parameter] public ISet<int>? SelectionLot { get; set; }
|
|
|
|
/// <summary>
|
|
/// ⚠️ EventCallback, et non un Action brut : coché depuis un tome niché dans ce bloc, l'écran
|
|
/// du catalogue (le VRAI propriétaire de la sélection) ne serait sinon jamais prévenu du
|
|
/// changement, et sa barre de sélection resterait figée sur l'état d'avant — le second piège
|
|
/// Blazor de CLAUDE.md.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<(int LivreId, bool Coche)> OnSelectionner { get; set; }
|
|
|
|
private (int Possedes, int Total) Compte =>
|
|
Totaux is not null && Totaux.TryGetValue(Entree.Serie!.Id, out var connu)
|
|
? connu
|
|
: (Entree.NombreLivres, Entree.NombreLivres);
|
|
|
|
private int Possedes => Compte.Possedes;
|
|
|
|
private int Total => Compte.Total;
|
|
|
|
private bool Deplie => Ouverts.Contains(Entree.Serie!.Id);
|
|
|
|
private void Basculer()
|
|
{
|
|
if (!Ouverts.Add(Entree.Serie!.Id))
|
|
{
|
|
Ouverts.Remove(Entree.Serie!.Id);
|
|
}
|
|
}
|
|
}
|