Une seule entrée d'ajout : « Ajouter un ouvrage »
Le geste réel est « j'ai un truc avec un code-barres », pas « je vais cataloguer un livre » : obliger à savoir d'avance ce qu'on tient était un détour, d'autant que le décodage distingue déjà un 978/979 d'un 977. Le même écran est atteint depuis le catalogue et depuis les revues. ⚠️ Le MODÈLE ne bouge pas : une revue reste une Revue, jamais un Livre. Fondre les deux tables aurait obligé à répéter « et qui n'est pas une revue » à chaque lecture du catalogue, des compteurs, des doublons, des séries et des bibliographies. - /ajout (alias /ajout/isbn) : « Ajouter un ouvrage », deux saisies manuelles distinctes puisque, sans code à lire, il faut bien trancher soi-même. - Nouvelle page /revues/ajout : titre, ISSN et éditeur, puis la fiche de la revue. Elle remplace le champ posé au milieu de la liste des revues. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,28 @@
|
|||||||
|
@page "/ajout"
|
||||||
@page "/ajout/isbn"
|
@page "/ajout/isbn"
|
||||||
@inject ServiceLivresApi Api
|
@inject ServiceLivresApi Api
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject EtatReseau Reseau
|
@inject EtatReseau Reseau
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<PageTitle>MaBibli — ajouter par ISBN</PageTitle>
|
<PageTitle>MaBibli — ajouter un ouvrage</PageTitle>
|
||||||
|
|
||||||
<h1 class="titre-page">Ajouter par ISBN</h1>
|
@*
|
||||||
|
Entrée UNIQUE d'ajout, atteinte depuis le catalogue comme depuis les revues.
|
||||||
|
|
||||||
|
Le geste réel est « j'ai un truc avec un code-barres », pas « je vais cataloguer un livre » :
|
||||||
|
obliger à savoir d'avance ce qu'on tient était un détour, d'autant que le décodage sait
|
||||||
|
déjà distinguer un 978/979 (livre) d'un 977 (publication en série). Le modèle, lui, ne
|
||||||
|
bouge pas : une revue reste une Revue, jamais un Livre — voir CLAUDE.md.
|
||||||
|
*@
|
||||||
|
<h1 class="titre-page">Ajouter un ouvrage</h1>
|
||||||
|
|
||||||
@if (_etape == Etape.Saisie)
|
@if (_etape == Etape.Saisie)
|
||||||
{
|
{
|
||||||
<p class="message-discret">
|
<p class="message-discret">
|
||||||
Scannez le code-barres, ou saisissez l'ISBN imprimé sur le livre.
|
Scannez le code-barres, ou saisissez le code imprimé sur l'ouvrage — livre ou
|
||||||
Une douchette USB fonctionne telle quelle : le champ est déjà actif.
|
magazine, l'application reconnaît lequel. Une douchette USB fonctionne telle quelle :
|
||||||
|
le champ est déjà actif.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@*
|
@*
|
||||||
@@ -57,7 +67,11 @@
|
|||||||
@onclick="OuvrirScanner">
|
@onclick="OuvrirScanner">
|
||||||
Scanner
|
Scanner
|
||||||
</button>
|
</button>
|
||||||
<a class="bouton bouton-discret" href="ajout/manuel">Saisir à la main</a>
|
@* Deux saisies manuelles, parce qu'il faut bien trancher soi-même quand aucun code
|
||||||
|
n'est là pour le faire : c'est le seul endroit où l'entrée unique doit poser la
|
||||||
|
question. *@
|
||||||
|
<a class="bouton bouton-discret" href="ajout/manuel">Saisir un livre</a>
|
||||||
|
<a class="bouton bouton-discret" href="revues/ajout">Saisir une revue</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
@page "/revues/ajout"
|
||||||
|
@inject ServiceLivresApi Api
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject EtatReseau Reseau
|
||||||
|
|
||||||
|
<PageTitle>MaBibli — saisir une revue</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="titre-page">Saisir une revue</h1>
|
||||||
|
|
||||||
|
@*
|
||||||
|
Le pendant de « Saisir un livre à la main » : ce qu'on fait quand le code-barres est absent,
|
||||||
|
abîmé, ou que la BnF ne connaît pas le titre. L'entrée normale reste « Ajouter un ouvrage »,
|
||||||
|
qui scanne et reconnaît un code 977 tout seul.
|
||||||
|
*@
|
||||||
|
<p class="message-discret">
|
||||||
|
L'entrée habituelle est <a href="ajout">Ajouter un ouvrage</a> : le code-barres d'un
|
||||||
|
magazine commence par <span class="code-isbn">977</span> et l'application le reconnaît
|
||||||
|
seule. Ce formulaire sert quand il n'y a pas de code à lire.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if (!Reseau.EnLigne)
|
||||||
|
{
|
||||||
|
<p class="message-avertissement" role="status">
|
||||||
|
Hors ligne : l'enregistrement passe par le serveur. Il redeviendra possible au retour
|
||||||
|
du réseau.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_erreur is not null)
|
||||||
|
{
|
||||||
|
<p class="message-erreur" role="alert">@_erreur</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">Titre<span class="obligatoire"> *</span></span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_titre" @bind:event="oninput"
|
||||||
|
placeholder="Médor" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">
|
||||||
|
ISSN<span class="champ-aide"> (facultatif — huit chiffres, imprimé près du code-barres)</span>
|
||||||
|
</span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_issn" @bind:event="oninput"
|
||||||
|
placeholder="2466-6718" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">Éditeur<span class="champ-aide"> (facultatif)</span></span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_editeur" @bind:event="oninput"
|
||||||
|
autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<p class="champ-aide">
|
||||||
|
Les numéros s'ajoutent ensuite sur la fiche de la revue : une fiche par revue, les numéros
|
||||||
|
à l'intérieur.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<nav class="actions-flottantes">
|
||||||
|
<button type="button" class="bouton bouton-principal"
|
||||||
|
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
|
||||||
|
title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
|
||||||
|
@onclick="EnregistrerAsync">
|
||||||
|
@(_enCours ? "Enregistrement…" : "Créer la revue")
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _titre = string.Empty;
|
||||||
|
private string _issn = string.Empty;
|
||||||
|
private string _editeur = string.Empty;
|
||||||
|
private bool _enCours;
|
||||||
|
private string? _erreur;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crée la revue — ou retrouve celle qui existe déjà — puis ouvre sa fiche.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Le serveur fait « créer ou retrouver » sur l'ISSN <b>ou</b> le titre : saisir deux fois
|
||||||
|
/// la même revue mène à la même fiche au lieu d'un refus. C'est aussi ce qui permet à une
|
||||||
|
/// revue saisie ici d'apprendre son ISSN au premier scan.
|
||||||
|
/// <para>
|
||||||
|
/// On enchaîne sur la fiche plutôt que de revenir à la liste : le geste n'est jamais
|
||||||
|
/// « créer une revue », c'est toujours « ranger un numéro ».
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
private async Task EnregistrerAsync()
|
||||||
|
{
|
||||||
|
_enCours = true;
|
||||||
|
_erreur = null;
|
||||||
|
|
||||||
|
var resultat = await Api.CreerRevueAsync(new EnregistrementRevue
|
||||||
|
{
|
||||||
|
Titre = _titre,
|
||||||
|
Issn = string.IsNullOrWhiteSpace(_issn) ? null : _issn,
|
||||||
|
Editeur = string.IsNullOrWhiteSpace(_editeur) ? null : _editeur,
|
||||||
|
});
|
||||||
|
|
||||||
|
_enCours = false;
|
||||||
|
|
||||||
|
if (!resultat.EstOk || resultat.Valeur is not { } revue)
|
||||||
|
{
|
||||||
|
_erreur = resultat.Erreur ?? "La revue n'a pas pu être créée.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigation.NavigateTo($"revues/{revue.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -160,12 +160,12 @@ else if (_livres is not null)
|
|||||||
<nav class="actions-flottantes">
|
<nav class="actions-flottantes">
|
||||||
@if (Reseau.EnLigne)
|
@if (Reseau.EnLigne)
|
||||||
{
|
{
|
||||||
<a class="bouton bouton-principal" href="ajout/isbn">Ajouter un livre</a>
|
<a class="bouton bouton-principal" href="ajout">Ajouter un ouvrage</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="bouton bouton-principal" disabled
|
<button type="button" class="bouton bouton-principal" disabled
|
||||||
title="@EtatReseau.MotifHorsLigne">Ajouter un livre</button>
|
title="@EtatReseau.MotifHorsLigne">Ajouter un ouvrage</button>
|
||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -12,26 +12,6 @@
|
|||||||
douze fiches. Les revues ne sont pas au catalogue : ce ne sont pas des livres.
|
douze fiches. Les revues ne sont pas au catalogue : ce ne sont pas des livres.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@if (_erreur is not null)
|
|
||||||
{
|
|
||||||
<p class="message-erreur" role="alert">@_erreur</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="barre-recherche">
|
|
||||||
<input class="champ-saisie" type="text" placeholder="Nom d'une revue…"
|
|
||||||
@bind="_nouvelle" @bind:event="oninput" @onkeydown="SurTouche"
|
|
||||||
aria-label="Nom de la nouvelle revue" autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-formulaire">
|
|
||||||
<button type="button" class="bouton bouton-principal"
|
|
||||||
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_nouvelle))"
|
|
||||||
title="@MotifBlocage"
|
|
||||||
@onclick="CreerAsync">
|
|
||||||
Ajouter la revue
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (_revues is null)
|
@if (_revues is null)
|
||||||
{
|
{
|
||||||
<p class="message-discret" role="status">Chargement…</p>
|
<p class="message-discret" role="status">Chargement…</p>
|
||||||
@@ -39,8 +19,9 @@
|
|||||||
else if (_revues.Count == 0)
|
else if (_revues.Count == 0)
|
||||||
{
|
{
|
||||||
<p class="message-discret">
|
<p class="message-discret">
|
||||||
Aucune revue. Scannez le code-barres d'un magazine, ou saisissez son nom ci-dessus :
|
Aucune revue. Scannez le code-barres d'un magazine depuis « Ajouter un ouvrage » :
|
||||||
le code d'une revue commence par 977 et porte un ISSN, pas un ISBN.
|
le code d'une revue commence par 977 et porte un ISSN, pas un ISBN. Sans code à lire,
|
||||||
|
<a href="revues/ajout">saisissez-la à la main</a>.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -80,13 +61,27 @@ else
|
|||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@*
|
||||||
|
⚠️ Le champ « nom d'une nouvelle revue » a quitté cet écran : l'ajout passe par la MÊME
|
||||||
|
entrée que celle du catalogue, « Ajouter un ouvrage », qui scanne et reconnaît seule un
|
||||||
|
code 977. Deux points de départ selon ce qu'on croit tenir obligeaient à trancher avant
|
||||||
|
de regarder le code-barres.
|
||||||
|
*@
|
||||||
|
<nav class="actions-flottantes">
|
||||||
|
@if (Reseau.EnLigne)
|
||||||
|
{
|
||||||
|
<a class="bouton bouton-principal" href="ajout">Ajouter un ouvrage</a>
|
||||||
|
<a class="bouton bouton-discret" href="revues/ajout">Saisir une revue</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-principal" disabled
|
||||||
|
title="@EtatReseau.MotifHorsLigne">Ajouter un ouvrage</button>
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private IReadOnlyList<RevueDto>? _revues;
|
private IReadOnlyList<RevueDto>? _revues;
|
||||||
private string _nouvelle = string.Empty;
|
|
||||||
private bool _enCours;
|
|
||||||
private string? _erreur;
|
|
||||||
|
|
||||||
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
|
||||||
|
|
||||||
private static string Quand(NumeroRevueDto numero) =>
|
private static string Quand(NumeroRevueDto numero) =>
|
||||||
numero.DateParution is { } date ? $"({date.ToLocalTime():MM/yyyy})" : string.Empty;
|
numero.DateParution is { } date ? $"({date.ToLocalTime():MM/yyyy})" : string.Empty;
|
||||||
@@ -105,30 +100,4 @@ else
|
|||||||
|
|
||||||
private async Task ChargerAsync() => _revues = await Api.ListerRevuesAsync();
|
private async Task ChargerAsync() => _revues = await Api.ListerRevuesAsync();
|
||||||
|
|
||||||
private async Task SurTouche(KeyboardEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_nouvelle) && Reseau.EnLigne)
|
|
||||||
{
|
|
||||||
await CreerAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreerAsync()
|
|
||||||
{
|
|
||||||
_enCours = true;
|
|
||||||
_erreur = null;
|
|
||||||
|
|
||||||
var resultat = await Api.CreerRevueAsync(new EnregistrementRevue { Titre = _nouvelle });
|
|
||||||
|
|
||||||
_enCours = false;
|
|
||||||
|
|
||||||
if (!resultat.EstOk)
|
|
||||||
{
|
|
||||||
_erreur = resultat.Erreur;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_nouvelle = string.Empty;
|
|
||||||
await ChargerAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user