+ @*
+ Titre, identité de la revue et actions sur la MÊME ligne (lot W) : disposition
+ symétrique de celle de la fiche d'une série, et déjà tenue par la bibliographie et la
+ liste des auteurs. Elle rend les deux actions visibles sans descendre au bas d'une
+ collection de trente numéros.
+ *@
+
+
+
@_revue.Titre
-
- @if (!string.IsNullOrWhiteSpace(_revue.Issn))
+
+ @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 (!ModeEdition)
{
- @* 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)
+
+ @* Ajouter un numéro reste en CONSULTATION, et ce n'est pas une entorse à
+ « consulter d'abord, modifier ensuite » : le geste ne touche pas à la fiche de
+ la revue, il range un objet de plus — comme noter un prêt depuis la fiche d'un
+ livre. Ce sont la modification et le retrait d'un numéro qui vivent, elles,
+ uniquement dans l'écran d'édition. *@
+
+
+ @if (Reseau.EnLigne)
+ {
+ Modifier la revue
+ }
+ else
+ {
+
+ }
+
+
+ @*
+ ⚠️ 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.
+
+ }
+
+
+
+
+
+
+ }
+
+ @if (ModeEdition)
+ {
+
La revue
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (_confirmeSuppression)
+ {
+
+ Les @_revue.Numeros.Count numéro(s) recensés disparaîtront avec elle : ils
+ n'existent que par leur revue.
+
+ }
+ }
+
Numéros possédés
@if (_revue.Numeros.Count == 0)
@@ -46,12 +160,13 @@ 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. *@
+ son ordre de lecture. L'ordre est celui rendu par l'API — les numéros sans date
+ ferment la liste, une date inconnue n'étant pas une date ancienne. *@
-
+ @*
+ ⚠️ Modifier et retirer un numéro n'existent QUE dans l'écran
+ d'édition : en consultation, ces deux boutons se déclenchaient sous
+ le pouce en faisant défiler la collection — même raison que les
+ flèches d'ordre d'une série, reléguées sur leur propre écran.
+ *@
+ @if (ModeEdition)
+ {
+
+
-
-
+ @* Un retrait est destructeur et ne se défait pas : il se
+ confirme, comme la suppression d'un livre ou d'une revue. *@
+
+
+ }
}
}
}
-
-
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 {
@@ -232,16 +288,54 @@ else
private string? _issn;
private string? _editeur;
- /// Saisie du numéro qu'on ajoute.
+ /// Saisie du numéro qu'on ajoute, et repli du formulaire qui la porte.
private AjoutNumeroRevue _saisie = new();
+ private bool _ajout;
/// Numéro en cours de modification, et sa saisie — null = aucun.
private int? _edition;
private AjoutNumeroRevue _saisieEdition = new();
+ /// Numéro dont le retrait attend confirmation — null = aucun.
+ private int? _confirmeRetrait;
+
+ ///
+ /// Écran d'édition de la revue : nom, ISSN, éditeur, suppression, et la seule place où un
+ /// numéro se modifie ou se retire.
+ ///
+ ///
+ /// ⚠️ Le mode est dans l'ADRESSE, pas dans un booléen interne : convention du projet depuis
+ /// la fiche livre. Comme les deux routes partagent le même paramètre Id, le routeur
+ /// ne déclenche aucun nouveau rendu en passant de l'une à l'autre — d'où l'abonnement
+ /// à LocationChanged, sans lequel l'URL changerait et l'écran resterait le même.
+ ///
+ private bool ModeEdition =>
+ Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath.TrimEnd('/')
+ .EndsWith("/edition", StringComparison.OrdinalIgnoreCase);
+
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
- protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
+ protected override void OnInitialized()
+ {
+ Reseau.Change += SurChangementReseau;
+ Navigation.LocationChanged += SurNavigation;
+ }
+
+ ///
+ /// Quitter l'édition referme ce qui n'a de sens que là : un formulaire de numéro resté
+ /// ouvert, et une confirmation de retrait en attente.
+ ///
+ private void SurNavigation(object? _, LocationChangedEventArgs __) => InvokeAsync(() =>
+ {
+ if (!ModeEdition)
+ {
+ _edition = null;
+ _confirmeRetrait = null;
+ _confirmeSuppression = false;
+ }
+
+ StateHasChanged();
+ });
private void SurChangementReseau() => _ = InvokeAsync(async () =>
{
@@ -249,7 +343,11 @@ else
StateHasChanged();
});
- public void Dispose() => Reseau.Change -= SurChangementReseau;
+ public void Dispose()
+ {
+ Reseau.Change -= SurChangementReseau;
+ Navigation.LocationChanged -= SurNavigation;
+ }
protected override Task OnParametersSetAsync() => ChargerAsync();
@@ -266,13 +364,22 @@ else
_editeur = _revue.Editeur;
}
+ // Un numéro lu au code-barres ouvre le formulaire de lui-même : on vient de scanner,
+ // demander un clic de plus pour saisir ce qu'on tient en main serait un détour.
if (!_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne))
{
_numeroPropose = true;
_saisie.Numero = NumeroScanne.Trim();
+ _ajout = true;
}
}
+ private void BasculerAjout()
+ {
+ _erreur = null;
+ _ajout = !_ajout;
+ }
+
private void Appliquer(RevueDto revue)
{
_revue = revue;
@@ -318,6 +425,7 @@ else
private void OuvrirEdition(NumeroRevueDto numero)
{
_erreur = null;
+ _confirmeRetrait = null;
_edition = numero.Id;
_saisieEdition = new AjoutNumeroRevue
{
@@ -329,6 +437,8 @@ else
};
}
+ private void FermerEdition() => _edition = null;
+
private async Task EnregistrerNumeroAsync()
{
if (_edition is not { } numeroId
@@ -358,9 +468,19 @@ else
private async Task RetirerAsync(NumeroRevueDto numero)
{
+ // Premier clic : on demande confirmation. Un numéro retiré ne se récupère pas, et la
+ // liste s'égrène sous le pouce.
+ if (_confirmeRetrait != numero.Id)
+ {
+ _erreur = null;
+ _confirmeRetrait = numero.Id;
+ return;
+ }
+
_enCours = true;
_erreur = await Api.RetirerNumeroAsync(numero.Id);
_enCours = false;
+ _confirmeRetrait = null;
// Le numéro retiré était peut-être celui qu'on modifiait : un formulaire ouvert sur une
// ligne disparue n'aurait plus rien à enregistrer.
@@ -392,6 +512,10 @@ else
}
Appliquer(resultat.Valeur!);
+
+ // La fiche est à jour : on retombe sur la consultation, comme après l'édition d'un
+ // livre ou d'une série. Rester en édition inviterait à réenregistrer.
+ Navigation.NavigateTo($"revues/{Id}");
}
private async Task SupprimerAsync()
diff --git a/MaBibli.Client/Services/RemonteeRoutes.cs b/MaBibli.Client/Services/RemonteeRoutes.cs
index 685c569..d983a71 100644
--- a/MaBibli.Client/Services/RemonteeRoutes.cs
+++ b/MaBibli.Client/Services/RemonteeRoutes.cs
@@ -67,6 +67,7 @@ public static class RemonteeRoutes
// Revues. ⚠️ « /revues/ajout » est écrit AVANT « /revues/{id} » : « ajout » n'est pas un
// identifiant, mais l'ordre de lecture est ce qui le garantit sans ambiguïté.
("/revues/ajout", "/revues"),
+ ("/revues/{id}/edition", "/revues/{id}"),
("/revues/{id}", "/revues"),
("/revues", Racine),
diff --git a/MaBibli.Tests/RemonteeRoutesTests.cs b/MaBibli.Tests/RemonteeRoutesTests.cs
index 7b91cda..a2536fc 100644
--- a/MaBibli.Tests/RemonteeRoutesTests.cs
+++ b/MaBibli.Tests/RemonteeRoutesTests.cs
@@ -29,6 +29,7 @@ public class RemonteeRoutesTests
[InlineData("/ajout/manuel", "/")]
// Revues
[InlineData("/revues/ajout", "/revues")]
+ [InlineData("/revues/12/edition", "/revues/12")]
[InlineData("/revues/12", "/revues")]
[InlineData("/revues", "/")]
// Auteurs
@@ -74,7 +75,7 @@ public class RemonteeRoutesTests
/// deviné, et jamais rien d'externe.
///
[Theory]
- [InlineData("/revues/12/edition", "/revues")]
+ [InlineData("/revues/12/inconnu", "/revues")]
[InlineData("/series/7/inconnu", "/series")]
[InlineData("/inconnu/total", "/")]
[InlineData("/livres", "/")]
diff --git a/MaBibli.Tests/ServiceRevuesTests.cs b/MaBibli.Tests/ServiceRevuesTests.cs
index c8675be..7d8367a 100644
--- a/MaBibli.Tests/ServiceRevuesTests.cs
+++ b/MaBibli.Tests/ServiceRevuesTests.cs
@@ -307,6 +307,65 @@ public class ServiceRevuesTests : IDisposable
Assert.Equal(2, (await _db.ArticlesUne.ToListAsync()).Count);
}
+ ///
+ /// L'écran d'édition (lot W) affiche telle quelle la revue rendue par la
+ /// modification d'un numéro : elle doit donc revenir déjà rangée.
+ ///
+ ///
+ /// ⚠️ C'est ce qui empêche de croire qu'on a mal saisi une date : corriger la parution d'un
+ /// numéro le déplace dans la liste, et l'écran ne recharge rien de son côté. Les numéros
+ /// sans date ferment la liste, ici comme partout — une date inconnue n'est pas une date
+ /// ancienne.
+ ///
+ [Fact]
+ public async Task Corriger_la_parution_range_le_numero_a_sa_place()
+ {
+ var revue = await RevueAsync();
+
+ await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
+ {
+ Numero = "43",
+ DateParution = new DateTime(2024, 6, 1, 0, 0, 0, DateTimeKind.Utc),
+ });
+
+ var cree = (await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
+ {
+ Numero = "hors-série 1",
+ })).Revue!;
+
+ Assert.Equal(["43", "hors-série 1"], cree.Numeros.Select(n => n.Numero));
+
+ var horsSerie = cree.Numeros.First(n => n.Numero == "hors-série 1").Id;
+
+ var apres = await _revues.ModifierNumeroAsync(horsSerie, new AjoutNumeroRevue
+ {
+ Numero = "hors-série 1",
+ DateParution = new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc),
+ });
+
+ Assert.Equal(["hors-série 1", "43"], apres.Revue!.Numeros.Select(n => n.Numero));
+ }
+
+ ///
+ /// Retirer un numéro depuis l'écran d'édition ne touche qu'à lui : les autres parutions et
+ /// la fiche de la revue restent en place.
+ ///
+ [Fact]
+ public async Task Retirer_un_numero_laisse_la_revue_et_ses_autres_numeros()
+ {
+ var revue = await RevueAsync();
+
+ await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = "43" });
+ var cree = (await _revues.AjouterNumeroAsync(
+ revue.Id, new AjoutNumeroRevue { Numero = "44" })).Revue!;
+
+ Assert.True(await _revues.RetirerNumeroAsync(cree.Numeros.First(n => n.Numero == "44").Id));
+
+ var restantes = await _revues.ListerAsync();
+
+ Assert.Equal(["43"], Assert.Single(restantes).Numeros.Select(n => n.Numero));
+ }
+
public void Dispose()
{
_db.Dispose();