MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts, scan de code-barres, consultation hors-ligne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
@page "/souhaits/{Id:int}/edition"
|
||||
@inject ServiceLivresApi Api
|
||||
@inject EtatReseau Reseau
|
||||
@inject NavigationManager Navigation
|
||||
@implements IDisposable
|
||||
|
||||
@*
|
||||
Écran dédié plutôt qu'un formulaire déplié dans la liste : convention du projet depuis la
|
||||
fiche livre — le mode est dans l'ADRESSE, jamais dans un booléen interne. C'est ce qui rend
|
||||
le bouton « retour » du navigateur une sortie d'édition naturelle, et ce qui empêche de
|
||||
modifier une envie en faisant défiler sa liste.
|
||||
|
||||
⚠️ Cette page ne partage aucun paramètre avec une autre route : contrairement à la fiche
|
||||
livre ou à la fiche série, elle n'a pas besoin de s'abonner à LocationChanged. L'abonnement
|
||||
ci-dessous ne sert qu'à suivre l'état du réseau.
|
||||
*@
|
||||
|
||||
<PageTitle>MaBibli — modifier une envie</PageTitle>
|
||||
|
||||
<h1 class="titre-page">Modifier une envie</h1>
|
||||
|
||||
@if (_erreur is not null)
|
||||
{
|
||||
<p class="message-erreur" role="alert">@_erreur</p>
|
||||
}
|
||||
|
||||
@if (_introuvable)
|
||||
{
|
||||
@* Hors-ligne, « supprimée » et « pas encore enregistrée ici » sont indiscernables : on ne
|
||||
tranche pas à la place de l'utilisateur. Même règle que pour une fiche livre absente. *@
|
||||
<p class="message-discret">
|
||||
@(Reseau.EnLigne
|
||||
? "Cette envie n'existe plus dans votre liste."
|
||||
: "Cette envie n'est pas dans les données enregistrées sur cet appareil.")
|
||||
</p>
|
||||
}
|
||||
else if (_saisie is null)
|
||||
{
|
||||
<Patience Message="Ouverture de l'envie…" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="formulaire">
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Titre <span class="obligatoire" aria-hidden="true">*</span></span>
|
||||
<input class="champ-saisie" type="text" @bind="_saisie.Titre" @bind:event="oninput"
|
||||
autocomplete="off" />
|
||||
</label>
|
||||
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Auteur</span>
|
||||
<input class="champ-saisie" type="text" @bind="_saisie.Auteur" @bind:event="oninput"
|
||||
autocomplete="off" />
|
||||
</label>
|
||||
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Éditeur</span>
|
||||
<input class="champ-saisie" type="text" @bind="_saisie.Editeur" @bind:event="oninput"
|
||||
autocomplete="off" />
|
||||
</label>
|
||||
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Année</span>
|
||||
<input class="champ-saisie" type="text" @bind="_saisie.Annee" @bind:event="oninput"
|
||||
placeholder="2012" autocomplete="off" />
|
||||
</label>
|
||||
|
||||
@* Le champ de saisie garde la valeur nue : découper à la frappe se battrait avec le
|
||||
curseur. Les tirets sont une affaire d'affichage, pas de saisie. *@
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">ISBN <span class="champ-aide">(facultatif)</span></span>
|
||||
<input class="champ-saisie" type="text" inputmode="numeric"
|
||||
@bind="_saisie.Isbn" @bind:event="oninput" placeholder="978…" autocomplete="off" />
|
||||
</label>
|
||||
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Note</span>
|
||||
<input class="champ-saisie" type="text" @bind="_saisie.Note" @bind:event="oninput"
|
||||
placeholder="« demandé à Noël », « en poche seulement »…" autocomplete="off" />
|
||||
</label>
|
||||
|
||||
@if (!Reseau.EnLigne)
|
||||
{
|
||||
<p class="message-avertissement" role="status">@EtatReseau.MotifHorsLigne</p>
|
||||
}
|
||||
|
||||
<div class="actions-formulaire">
|
||||
<button type="button" class="bouton bouton-principal"
|
||||
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_saisie.Titre))"
|
||||
title="@MotifBlocage" @onclick="EnregistrerAsync">
|
||||
@(_enCours ? "Enregistrement…" : "Enregistrer")
|
||||
</button>
|
||||
|
||||
<a class="bouton bouton-discret" href="souhaits">Annuler</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public int Id { get; set; }
|
||||
|
||||
private EnregistrementSouhait? _saisie;
|
||||
private bool _introuvable;
|
||||
private bool _enCours;
|
||||
private string? _erreur;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Reseau.Change += SurChangementReseau;
|
||||
await ChargerAsync();
|
||||
}
|
||||
|
||||
private void SurChangementReseau() => _ = InvokeAsync(StateHasChanged);
|
||||
|
||||
public void Dispose() => Reseau.Change -= SurChangementReseau;
|
||||
|
||||
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
||||
|
||||
/// <summary>
|
||||
/// Reprend l'envie depuis la <b>liste</b>, et non depuis un point d'entrée dédié.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// L'API n'expose pas <c>GET /api/souhaits/{id}</c>, et il n'y a pas lieu de l'ajouter : la
|
||||
/// liste entière est déjà chargée, déjà mise en instantané, et c'est ce qui rend cet écran
|
||||
/// consultable hors-ligne — même si l'enregistrement, lui, y est refusé.
|
||||
/// </remarks>
|
||||
private async Task ChargerAsync()
|
||||
{
|
||||
var souhaits = await Api.ListerSouhaitsAsync();
|
||||
var souhait = souhaits.FirstOrDefault(s => s.Id == Id);
|
||||
|
||||
if (souhait is null)
|
||||
{
|
||||
_introuvable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// ⚠️ La couverture n'est PAS reprise ici : elle n'a pas de champ de saisie, et le
|
||||
// service la conserve donc telle qu'elle est en base. La valeur portée par le DTO peut
|
||||
// en outre être déduite de l'ISBN — la figer serait écrire en base ce qui se calcule.
|
||||
_saisie = new EnregistrementSouhait
|
||||
{
|
||||
Titre = souhait.Titre,
|
||||
Auteur = souhait.Auteur,
|
||||
Editeur = souhait.Editeur,
|
||||
Annee = souhait.Annee,
|
||||
Isbn = souhait.Isbn,
|
||||
Note = souhait.Note,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task EnregistrerAsync()
|
||||
{
|
||||
if (_saisie is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enCours = true;
|
||||
_erreur = null;
|
||||
|
||||
var resultat = await Api.ModifierSouhaitAsync(Id, _saisie);
|
||||
|
||||
_enCours = false;
|
||||
|
||||
if (!resultat.EstOk)
|
||||
{
|
||||
// Le message vient de l'API : doublon d'une autre envie, ISBN invalide, ou hors
|
||||
// ligne. Aucun code HTTP brut n'arrive jusqu'ici.
|
||||
_erreur = resultat.Erreur;
|
||||
return;
|
||||
}
|
||||
|
||||
Navigation.NavigateTo("souhaits");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user