diff --git a/MaBibli.Client/Pages/AjoutSouhait.razor b/MaBibli.Client/Pages/AjoutSouhait.razor index 915873b..d815bdf 100644 --- a/MaBibli.Client/Pages/AjoutSouhait.razor +++ b/MaBibli.Client/Pages/AjoutSouhait.razor @@ -76,9 +76,32 @@ title="@MotifBlocage" @onclick="ChercherParIsbnAsync"> @(_recherche ? "Recherche…" : "Chercher") + + @* + J2 : le cas d'usage est direct — le livre est en main, en librairie. C'est le même + composant de scan que l'écran d'ajout au catalogue, sans création de Livre au bout : + un souhait n'est pas une possession. + *@ + } +@if (_scan) +{ + +} + +@* L1 : la BnF met environ une seconde à répondre. Sans indicateur, l'écran ne dit rien et + l'on reclique — donc on relance l'appel. *@ +@if (_recherche) +{ + +} + @if (_erreur is not null) { @@ -196,6 +219,9 @@ private TriResultats _tri = TriResultats.Date; private bool _recherche; private bool _ajout; + + /// Le scanner est ouvert. Il ne l'est que depuis la voie « par ISBN ». + private bool _scan; private string? _erreur; protected override void OnInitialized() => Reseau.Change += SurChangementReseau; @@ -239,8 +265,37 @@ } } + private void OuvrirScanner() + { + _erreur = null; + _candidats = []; + _avertissements = []; + _scan = true; + } + + /// + /// Enchaîne la recherche sur le code lu, sans bouton à confirmer. + /// + /// + /// Le code reste dans le champ : un décodage erroné se corrige et se relance. C'est la + /// décision déjà actée pour l'écran d'ajout au catalogue, et la seule cohérente avec une + /// douchette, qui envoie Entrée d'elle-même. + /// + /// ⚠️ L'add-on EAN-2 (numéro de parution) est ignoré ici, et ce n'est pas un oubli : il ne + /// concerne que les revues, qui ne se souhaitent pas — cet écran alimente une liste + /// d'œuvres. Un code de périodique est d'ailleurs intercepté plus bas et nommé. + /// + /// + private async Task SurCodeDetecteAsync(CodeScanne lecture) + { + _scan = false; + _isbn = lecture.Code; + await ChercherParIsbnAsync(); + } + private void Basculer(Mode mode) { + _scan = false; _mode = mode; _focusAFaire = true; _erreur = null; diff --git a/MaBibli.Client/Pages/Serie.razor b/MaBibli.Client/Pages/Serie.razor index d2d7cce..ac7a7c2 100644 --- a/MaBibli.Client/Pages/Serie.razor +++ b/MaBibli.Client/Pages/Serie.razor @@ -226,6 +226,34 @@ else title="@MotifBlocage" @onclick="ChercherLookupAsync"> @(_lookupEnCours ? "Recherche…" : "Préremplir") + + @* + J1 : le scanner s'ouvre ICI, dans la place visée, et non sur + l'écran d'ajout. + + ⚠️ C'est ce qui répond à l'exigence « le retour doit ramener + sur la série ET sur la place visée » : il n'y a pas de + retour, on n'a jamais quitté la place. Passer par /ajout + obligeait à revenir rattacher à la main — donc à risquer de + rattacher le mauvais tome. + *@ + + + @if (_lookupScan) + { + + } + + @* L1 : la cascade BnF puis OpenLibrary prend une seconde ou deux. *@ + @if (_lookupEnCours) + { + + } @if (_lookupCandidats.Count > 0) { @foreach (var candidat in _lookupCandidats) @@ -288,8 +316,57 @@ else @onclick="() => OuvrirRattachement(AjoutDepuisCatalogue)"> Depuis le catalogue + + @* + Lot P — le chemin inverse de « place vide → envie ». + + ⚠️ Ce geste n'ÉPARGNE QU'UNE RESAISIE : il crée une place ordinaire, LivreId à + NULL, titre repris de l'envie. Rien ne relie les deux en base, et c'est délibéré — + les séries sont COMMUNES au foyer, la liste d'envies est PERSONNELLE, et son sens + même est de préparer un cadeau sans que l'autre le voie venir. Une clé étrangère + vers l'envie afficherait « tome 3 souhaité par untel » à tout le monde. + *@ + + @if (_enviesOuvertes) + { +
+ @if (_enviesChargement) + { + + } + else if (_envies.Count == 0) + { +

+ Votre liste d'envies est vide — ou ne contient rien qui ne soit déjà un tome + de cette série. +

+ } + else + { +

+ Le tome est ajouté comme manquant, avec le titre de l'envie. + L'envie reste dans votre liste : rien n'a été acheté, et + personne d'autre ne verra qu'elle est à vous. +

+ + @foreach (var envie in _envies) + { + + } + } +
+ } + @if (_rattachement == AjoutDepuisCatalogue) {
@@ -419,6 +496,13 @@ else private IReadOnlyList _lookupCandidats = []; private bool _lookupEnCours; + /// Le scanner est ouvert sur la place visée par le lookup ISBN (lot J1). + private bool _lookupScan; + + private bool _enviesOuvertes; + private bool _enviesChargement; + private IReadOnlyList _envies = []; + private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne; private IReadOnlySet CheminRacine => new HashSet { Id }; @@ -616,11 +700,93 @@ else } } + private void OuvrirScanTome() + { + _erreur = null; + _lookupCandidats = []; + _lookupScan = true; + } + + /// + /// Enchaîne le lookup sur le code lu, sans quitter la place visée. + /// + /// + /// Le code reste dans le champ : un décodage erroné se corrige et se relance, comme sur + /// l'écran d'ajout au catalogue. + /// + /// ⚠️ Un code de périodique (977) est refusé plus bas par ChercherLookupAsync : + /// ElementSerie.LivreId ne pointe que vers Livre, et les revues restent + /// délibérément hors de ce flux. + /// + /// + private async Task SurCodeTomeAsync(CodeScanne lecture) + { + _lookupScan = false; + _lookupIsbn = lecture.Code; + await ChercherLookupAsync(); + } + + /// + /// Ouvre la liste des envies dans laquelle puiser un tome à recenser. + /// + /// + /// ⚠️ Les envies déjà présentes dans la série sont écartées de la liste : les proposer + /// mènerait droit au doublon, la place étant identifiée par son seul titre. + /// + private async Task OuvrirEnviesAsync() + { + _erreur = null; + _message = null; + _enviesOuvertes = true; + _enviesChargement = true; + + var toutes = await Api.ListerSouhaitsAsync(); + + var deja = new HashSet( + _serie?.Elements.Select(e => NormalisationTexte.Normaliser(e.Titre)) ?? [], + StringComparer.Ordinal); + + _envies = toutes + .Where(envie => !deja.Contains(NormalisationTexte.Normaliser(envie.Titre))) + .ToList(); + + _enviesChargement = false; + } + + /// + /// Recense un tome à partir d'une envie, sans rien lier. + /// + /// + /// La place créée est celle qu'on aurait saisie à la main : LivreId à NULL, + /// titre repris de l'envie. L'envie survit — c'est la règle déjà tenue par « une + /// envie déjà au catalogue est signalée, jamais supprimée », et à plus forte raison ici + /// puisque rien n'a été acheté. + /// + /// ⚠️ Ce qui doit rester invisible au foyer n'est pas l'existence du tome — on l'aurait + /// saisie à la main de toute façon — mais le lien avec la liste de quelqu'un. Il n'y + /// en a aucun en base. + /// + /// + private async Task AjouterDepuisEnvieAsync(SouhaitDto envie) + { + await EcrireAsync(() => Api.AjouterElementSerieAsync( + Id, new AjoutElementSerie { Titre = envie.Titre })); + + if (_erreur is null) + { + _enviesOuvertes = false; + _envies = []; + _message = $"« {envie.Titre} » est recensé comme tome manquant. " + + "Il reste dans votre liste d'envies."; + } + } + private void OuvrirLookup(ElementSerieDto element) { _lookupElement = element; _lookupIsbn = string.Empty; _lookupCandidats = []; + _lookupScan = false; _erreur = null; }