From e85867014cb46b00c6dfd60a29ba074bb3ed0650 Mon Sep 17 00:00:00 2001 From: mathieu Date: Sat, 22 Aug 2026 01:34:04 +0200 Subject: [PATCH] =?UTF-8?q?Replie=20les=20blocs=20de=20s=C3=A9rie=20au=20c?= =?UTF-8?q?atalogue,=20cycle=20par=20cycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le catalogue ne rangeait un livre que sous sa série directe : il n'y avait qu'un seul niveau, donc rien à replier « à chaque niveau ». Le regroupement devient un arbre — un cycle coiffe ses séries, comme sur /series — et chaque nœud porte sa bascule. Tout reste déplié par défaut ; un bloc replié annonce ce qu'il contient, et l'état de repli appartient à l'écran, sans quoi une frappe dans la recherche le perdrait. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 44 +++++ .../Composants/GroupeCatalogue.razor | 84 ++++++++++ MaBibli.Client/Pages/Catalogue.razor | 36 ++-- .../Services/GroupementCatalogue.cs | 155 ++++++++++++++---- MaBibli.Client/wwwroot/css/app.css | 27 +++ MaBibli.Tests/GroupementCatalogueTests.cs | 113 +++++++++++++ 6 files changed, 411 insertions(+), 48 deletions(-) create mode 100644 MaBibli.Client/Composants/GroupeCatalogue.razor diff --git a/CLAUDE.md b/CLAUDE.md index 996c4fd..735aa35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3320,6 +3320,50 @@ n'apparaît qu'une fois et toujours sous la même, la liste plate sans séries, tome, et le filtre qui ne se laisse pas défaire. C'est le même parti que `FiltreLivresLocal` : ce qui tient une règle sort du balisage. +### Les blocs se replient, et un cycle replie chacun de ses niveaux (2026-08-22) + +Demandé dans la foulée : « pouvoir réduire les groupes et ne garder que le nom de la série ; si +plusieurs niveaux, permettre de réduire chaque niveau ». + +⚠️ **Le catalogue ne connaissait qu'un seul niveau** : le regroupement rangeait chaque livre sous +sa série *directe*, sans jamais remonter au cycle. Il n'y avait donc pas de « chaque niveau » à +replier — c'est le regroupement lui-même qui a dû devenir un arbre, `EntreeCatalogue` portant +désormais des `SousEntrees`. Un cycle se lit maintenant au catalogue comme sur `/series`. + +| Décision | Pourquoi | +|---|---| +| **Tout est déplié par défaut**, on replie ce dont on ne veut pas | Règle déjà tenue par l'arbre des séries. L'inverse obligerait à ouvrir chaque bloc pour retrouver un livre qu'on voyait très bien avant | +| Le bloc **replié annonce toujours son compte** (« 3 tomes ») | Sans lui, le compteur du haut (« 6 livres ») paraîtrait mentir | +| L'état de repli vit dans l'**écran**, pas dans les blocs | Le catalogue se relit **à chaque frappe** dans la recherche : un état porté par les composants se perdrait à chaque caractère. Vérifié — le repli survit à une recherche et à son effacement | +| Non persistant d'une visite à l'autre | Confort de lecture, pas propriété de la série — même règle que `/series` | +| Le cycle se place à la place du **premier tome de sa descendance** | Prolongement exact de la règle de placement déjà actée : l'ordre général du catalogue ne bouge pas | +| Les sous-séries suivent leur **ordre de lecture** (`Serie.Position`), les tomes le leur | Deux écrans ne doivent pas donner deux ordres | + +⚠️ **Seuls les niveaux qui portent quelque chose existent.** Un cycle de cinq séries dont une +seule a un tome au catalogue ne produit pas quatre nœuds vides : on ne crée que la chaîne qui +mène aux livres présents. Un niveau vide serait une indentation qui ne se replierait sur rien — +et le titre d'une série dont on ne possède rien. + +⚠️ La remontée de parenté est **bornée par le nombre de séries**, comme celle du serveur : une +boucle résiduelle en base ferait sinon tourner le regroupement sans fin. Mieux vaut un bloc rangé +un cran trop haut qu'un catalogue qui ne s'affiche plus. Un test le verrouille. + +⚠️ **La bascule est un ` + + @* 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. *@ + + @Entree.Serie!.Titre + + @Entree.NombreLivres tome@(Entree.NombreLivres > 1 ? "s" : "") + + + + + @if (Deplie) + { + @if (Entree.SousEntrees.Count > 0) + { + + } + + @if (Entree.Livres.Count > 0) + { + + } + } + + +@code { + [Parameter, EditorRequired] public EntreeCatalogue Entree { get; set; } = default!; + + /// + /// Blocs REPLIÉS, par identifiant de série — et non dépliés : le défaut est de tout montrer. + /// + /// + /// État local de l'écran, volontairement non persistant, comme celui de l'arbre des séries : + /// 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. + /// + [Parameter, EditorRequired] public ISet Replies { get; set; } = new HashSet(); + + private bool Deplie => !Replies.Contains(Entree.Serie!.Id); + + private void Basculer() + { + if (!Replies.Add(Entree.Serie!.Id)) + { + Replies.Remove(Entree.Serie!.Id); + } + } +} diff --git a/MaBibli.Client/Pages/Catalogue.razor b/MaBibli.Client/Pages/Catalogue.razor index 1e196ea..9b06c12 100644 --- a/MaBibli.Client/Pages/Catalogue.razor +++ b/MaBibli.Client/Pages/Catalogue.razor @@ -157,29 +157,18 @@ else if (_livres is not null) eux : l'ordre général du catalogue ne bouge pas, on ne perd donc pas un livre qu'on cherchait à sa lettre. Le nom du bloc MÈNE À LA SÉRIE, où figurent aussi les tomes qu'on ne possède pas — ce que le catalogue, lui, ne peut pas montrer. + + Chaque bloc se REPLIE, et un cycle replie chacun de ses niveaux : un cycle de vingt + tomes occupait tout l'écran alors qu'on cherchait un livre rangé après lui. Replier ne + retire rien de la liste — le compteur du haut ne bouge pas, et le bloc replié dit + toujours ce qu'il contient. *@