@page "/revues/{Id:int}" @inject ServiceLivresApi Api @inject NavigationManager Navigation @inject EtatReseau Reseau @implements IDisposable MaBibli — @(_revue?.Titre ?? "revue") @if (_revue is null) {

@(_chargement ? "Chargement…" : "Cette revue n'est pas dans les données enregistrées sur cet appareil.")

} else {

@_revue.Titre

@if (!string.IsNullOrWhiteSpace(_revue.Issn)) { @* K2 : un ISSN se coupe TOUJOURS au même endroit, contrairement aux tranches d'un ISBN. Aucune table à embarquer, et aucune coupure ne peut être fausse. *@ ISSN @FormatageIssn.Afficher(_revue.Issn) } @if (!string.IsNullOrWhiteSpace(_revue.Editeur)) { @_revue.Editeur }

@if (_erreur is not null) { }

Numéros possédés

@if (_revue.Numeros.Count == 0) {

Aucun numéro recensé pour l'instant.

} else { @* Du plus récent au plus ancien : devant un kiosque, la question est « ai-je le dernier ? », pas « par quoi ai-je commencé ». C'est l'inverse d'une série, qui suit son ordre de lecture. *@ }

Ajouter un numéro

@* ⚠️ Le numéro se saisit à la main, et ce n'est pas un oubli : les deux chiffres de parution du code-barres ne sont PAS un numéro fiable (CLAUDE.md), et l'add-on EAN-2 qui le porterait vraiment n'a pas pu être vérifié sur un magazine réel. *@ @if (_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne)) {

Le code-barres portait « @NumeroScanne » à côté du code principal, ce qui est normalement le numéro de parution. Vérifiez-le sur la couverture avant d'enregistrer.

}

La revue

@if (_confirmeSuppression) {

Les @_revue.Numeros.Count numéro(s) recensés disparaîtront avec elle : ils n'existent que par leur revue.

} } @code { [Parameter] public int Id { get; set; } /// /// Numéro lu sur l'add-on EAN-2 du code-barres, transmis par l'écran de scan. /// /// /// ⚠️ Proposé, pas acquis. L'EAN-2 est normativement le numéro de parution, mais /// rien ne l'a confirmé sur des magazines réels : il atterrit donc dans le champ, où /// l'utilisateur le compare à la couverture et le corrige. C'est aussi pourquoi l'écran le /// dit explicitement au lieu de le glisser en douce. /// [SupplyParameterFromQuery(Name = "numero")] public string? NumeroScanne { get; set; } /// Le numéro scanné n'est proposé qu'une fois : ensuite, le champ appartient à l'utilisateur. private bool _numeroPropose; private RevueDto? _revue; private bool _chargement = true; private bool _enCours; private string? _erreur; private bool _confirmeSuppression; private string _titre = string.Empty; private string? _issn; private string? _editeur; /// Saisie du numéro qu'on ajoute. private AjoutNumeroRevue _saisie = new(); /// Numéro en cours de modification, et sa saisie — null = aucun. private int? _edition; private AjoutNumeroRevue _saisieEdition = new(); private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne; protected override void OnInitialized() => Reseau.Change += SurChangementReseau; private void SurChangementReseau() => _ = InvokeAsync(async () => { await ChargerAsync(); StateHasChanged(); }); public void Dispose() => Reseau.Change -= SurChangementReseau; protected override Task OnParametersSetAsync() => ChargerAsync(); private async Task ChargerAsync() { _chargement = true; _revue = (await Api.ListerRevuesAsync()).FirstOrDefault(r => r.Id == Id); _chargement = false; if (_revue is not null) { _titre = _revue.Titre; _issn = _revue.Issn; _editeur = _revue.Editeur; } if (!_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne)) { _numeroPropose = true; _saisie.Numero = NumeroScanne.Trim(); } } private void Appliquer(RevueDto revue) { _revue = revue; _titre = revue.Titre; _issn = revue.Issn; _editeur = revue.Editeur; } private async Task AjouterAsync() { if (string.IsNullOrWhiteSpace(_saisie.Numero) || !Reseau.EnLigne || _enCours) { return; } _enCours = true; _erreur = null; // La conversion de la date en UTC vit dans ChampsNumeroRevue : les deux formulaires qui // s'en servent ne peuvent donc pas l'oublier chacun de leur côté. var resultat = await Api.AjouterNumeroAsync(Id, _saisie); _enCours = false; if (!resultat.EstOk) { _erreur = resultat.Erreur; return; } Appliquer(resultat.Valeur!); _saisie = new AjoutNumeroRevue(); } /// /// Ouvre la modification d'un numéro, en recopiant ce qu'il porte déjà. /// /// /// La couverture et les articles à la une ne peuvent pas être connus à l'achat : on /// note un numéro quand on le range, et on en recopie la une plus tard. Sans cet écran, ces /// deux champs n'auraient existé qu'à la création, c'est-à-dire jamais. /// private void OuvrirEdition(NumeroRevueDto numero) { _erreur = null; _edition = numero.Id; _saisieEdition = new AjoutNumeroRevue { Numero = numero.Numero, DateParution = numero.DateParution, Note = numero.Note, CoverUrl = numero.CoverUrl, Articles = [.. numero.Articles], }; } private async Task EnregistrerNumeroAsync() { if (_edition is not { } numeroId || string.IsNullOrWhiteSpace(_saisieEdition.Numero) || !Reseau.EnLigne || _enCours) { return; } _enCours = true; _erreur = null; var resultat = await Api.ModifierNumeroAsync(numeroId, _saisieEdition); _enCours = false; if (!resultat.EstOk) { _erreur = resultat.Erreur; return; } Appliquer(resultat.Valeur!); _edition = null; } private async Task RetirerAsync(NumeroRevueDto numero) { _enCours = true; _erreur = await Api.RetirerNumeroAsync(numero.Id); _enCours = false; // Le numéro retiré était peut-être celui qu'on modifiait : un formulaire ouvert sur une // ligne disparue n'aurait plus rien à enregistrer. if (_edition == numero.Id) { _edition = null; } if (_erreur is null) { await ChargerAsync(); } } private async Task EnregistrerAsync() { _enCours = true; _erreur = null; var resultat = await Api.ModifierRevueAsync( Id, new EnregistrementRevue { Titre = _titre, Issn = _issn, Editeur = _editeur }); _enCours = false; if (!resultat.EstOk) { _erreur = resultat.Erreur; return; } Appliquer(resultat.Valeur!); } private async Task SupprimerAsync() { if (!_confirmeSuppression) { _confirmeSuppression = true; return; } _enCours = true; _erreur = await Api.SupprimerRevueAsync(Id); _enCours = false; if (_erreur is null) { Navigation.NavigateTo("revues"); } } }