- Cataloguer en rafale — nouvel écran /ajout/rafale : on scanne une pile de livres à la suite dans une zone de texte, chaque code est traité (BnF puis OpenLibrary), les doublons connus sont passés automatiquement. La collecte marche même hors-ligne. Le compte rendu liste maintenant les livres créés, en lien vers leur fiche, et reste consultable en revenant sur l'écran même après une rafale entièrement réussie. Le catalogue reconnaît un ISBN dans sa barre de recherche (13 ou 10 chiffres, avec ou sans tirets) : scanner un livre en main ouvre directement sa fiche s'il n'y en a qu'un. Un bouton « Scanner » l'alimente, actif hors-ligne. - Ajouter un tome à une série accepte aussi un ISBN dans le champ manuel : le catalogue est cherché d'abord (rattachement direct si un seul exemplaire), sinon la BnF prend le relais. Séries et sagas - Numéro de tome distinct de la position de lecture : on peut indiquer « c'est le tome 7 » même si on ne possède pas les six premiers ; l'ordre de lecture reste un réglage séparé (utile pour les préquelles). - Tri par numéro en plus du tri par ordre de lecture quand des tomes en portent un. - Panneau « Ajouter » regroupé et repliable sur la fiche d'une série (manuellement / en rafale / depuis le catalogue / depuis les envies), au lieu de quatre formulaires ouverts en permanence. - Filtre catalogue « sans couverture » pour repérer les livres à illustrer. Le catalogue groupe les tomes d'une même série sous un bloc repliable, avec un décompte plus clair (affichés / possédés / total). - Corrections directes sur la fiche - Effacer un prêt saisi par erreur (bouton ✕ sur chaque ligne, avec confirmation), sans passer par « rendre ». - Corriger une couverture manquante ou cassée en cliquant dessus : le champ d'adresse s'ouvre focalisé, Entrée enregistre. Étendu aux numéros de revue dans la dernière modification. - Les thèmes déjà utilisés dans la bibliothèque sont proposés à la frappe. - La recherche d'un livre à rattacher montre des suggestions dès le focus, sans attendre de taper. - Visuel : un rendu manquant après une écriture asynchrone dans le formulaire de livre, une bascule de rôle cassée, des débordements à 320 px, et le style d'un bouton-lien qui restait souligné.
551 lines
20 KiB
Plaintext
551 lines
20 KiB
Plaintext
@page "/livres/{Id:int}"
|
|
@page "/livres/{Id:int}/edition"
|
|
@inject ServiceLivresApi Api
|
|
@inject NavigationManager Navigation
|
|
@inject EtatReseau Reseau
|
|
@implements IDisposable
|
|
|
|
@*
|
|
Une seule page, deux modes (CLAUDE.md) :
|
|
|
|
— consultation par défaut. Ouvrir une fiche depuis le catalogue ne doit rien pouvoir casser,
|
|
surtout au doigt sur un téléphone.
|
|
— édition sur demande explicite, via son adresse propre « /livres/{id}/edition ». Le mode
|
|
n'est pas un booléen caché : il est dans l'URL, donc le bouton « retour » du navigateur
|
|
sort de l'édition, ce qui est exactement ce qu'on attend d'un geste mobile.
|
|
|
|
Restent manipulables SANS entrer en édition, parce que ce ne sont pas des modifications de
|
|
la fiche mais les gestes du quotidien : le statut de lecture (le plus fréquent de tous) et
|
|
les prêts (prêter, marquer rendu).
|
|
*@
|
|
|
|
<PageTitle>MaBibli — @(_livre?.Titre ?? "fiche du livre")</PageTitle>
|
|
|
|
<h1 class="titre-page">@(ModeEdition ? "Modifier la fiche" : "Fiche du livre")</h1>
|
|
|
|
@if (_introuvable)
|
|
{
|
|
@* Hors-ligne, « introuvable » ne veut pas dire « supprimé » : le livre peut simplement avoir
|
|
été ajouté après la dernière synchronisation. Le dire évite un contresens inquiétant. *@
|
|
<p class="message-erreur" role="alert">
|
|
@(Reseau.EnLigne
|
|
? "Ce livre n'existe pas (ou plus)."
|
|
: "Ce livre n'est pas dans les données enregistrées sur cet appareil. Il a peut-être été ajouté depuis la dernière synchronisation.")
|
|
</p>
|
|
<a class="bouton bouton-discret" href="">Retour au catalogue</a>
|
|
}
|
|
else if (_livre is null || _saisie is null)
|
|
{
|
|
<p class="message-discret">Chargement…</p>
|
|
}
|
|
else if (ModeEdition)
|
|
{
|
|
<FormulaireLivre Saisie="_saisie"
|
|
LibelleValidation="Enregistrer les modifications"
|
|
Erreur="@_erreur"
|
|
EnCours="_enregistrement"
|
|
MessageBlocage="@MotifBlocage"
|
|
OnValider="EnregistrerAsync"
|
|
OnAnnuler="RetourFiche" />
|
|
|
|
@* La suppression n'est offerte qu'ici : c'est le seul écran où l'on vient pour modifier. *@
|
|
<section class="zone-danger">
|
|
@if (!_confirmationSuppression)
|
|
{
|
|
<button type="button" class="bouton bouton-danger"
|
|
disabled="@(!Reseau.EnLigne)" title="@MotifBlocage"
|
|
@onclick="() => _confirmationSuppression = true">
|
|
Supprimer ce livre
|
|
</button>
|
|
}
|
|
else
|
|
{
|
|
<p class="message-avertissement">
|
|
Supprimer « @_saisie.Titre » définitivement ? L'historique de ses prêts partira avec.
|
|
@* Choix assumé : les prêts n'ont d'intérêt qu'attachés à un livre. Un historique
|
|
orphelin (« quelqu'un a emprunté quelque chose ») ne se lit plus. *@
|
|
@if (_livre.PreteA is { } emprunteur)
|
|
{
|
|
<span class="avertissement-fort">
|
|
Attention : ce livre est actuellement prêté à @emprunteur.
|
|
</span>
|
|
}
|
|
</p>
|
|
<div class="actions-formulaire">
|
|
<button type="button" class="bouton bouton-danger" disabled="@_suppression" @onclick="SupprimerAsync">
|
|
@(_suppression ? "Suppression…" : "Oui, supprimer")
|
|
</button>
|
|
<button type="button" class="bouton bouton-discret" disabled="@_suppression"
|
|
@onclick="() => _confirmationSuppression = false">
|
|
Annuler
|
|
</button>
|
|
</div>
|
|
}
|
|
</section>
|
|
}
|
|
else
|
|
{
|
|
@if (_erreur is not null)
|
|
{
|
|
<p class="message-erreur" role="alert">@_erreur</p>
|
|
}
|
|
|
|
<div class="fiche-entete">
|
|
@*
|
|
⚠️ Une couverture CASSÉE devient un point de correction : cliquer dessus ouvre son
|
|
adresse, focalisée, et « Entrée » enregistre. C'est là qu'on constate le défaut, et
|
|
c'est donc là qu'il faut pouvoir le réparer — passer par « Éditer » puis descendre
|
|
au dernier champ du formulaire pour corriger un lien mort était un détour.
|
|
|
|
Une couverture qui s'affiche, elle, s'agrandit comme avant.
|
|
*@
|
|
<Couverture Url="@_livre.CoverUrl" Titre="@_livre.Titre"
|
|
Classe="couverture-grande" Agrandissable="true"
|
|
OnCorriger="OuvrirCorrectionCouverture" />
|
|
|
|
<div class="fiche-infos">
|
|
<h2 class="fiche-titre">@_livre.Titre</h2>
|
|
|
|
@if (_livre.Auteurs.Count > 0)
|
|
{
|
|
@* Cliquer un auteur ramène au catalogue restreint à ses livres. *@
|
|
<p class="fiche-auteurs">
|
|
@foreach (var (auteur, index) in _livre.Auteurs.Select((a, i) => (a, i)))
|
|
{
|
|
@(index > 0 ? ", " : "")
|
|
<a class="lien-auteur" href="@($"?auteur={auteur.Id}")">@auteur.Nom</a>@if (auteur.Role != RoleAuteur.NonPrecise)
|
|
{
|
|
<span class="role-auteur"> (@Libelles.RoleAuteur(auteur.Role).ToLowerInvariant())</span>
|
|
}
|
|
}
|
|
</p>
|
|
}
|
|
|
|
@*
|
|
Les séries auxquelles ce livre appartient. Il peut y en avoir plusieurs — une
|
|
trilogie, une intégrale, un cycle — d'où une liste et non un champ.
|
|
⚠️ Lu depuis l'instantané des séries, donc disponible hors-ligne comme le reste.
|
|
*@
|
|
@foreach (var appartenance in _appartenances)
|
|
{
|
|
<p class="fiche-serie">
|
|
Tome @appartenance.Rang de
|
|
<a href="@($"series/{appartenance.Serie.Id}")">@appartenance.Serie.Titre</a>
|
|
</p>
|
|
}
|
|
|
|
<dl class="fiche-champs">
|
|
@if (!string.IsNullOrWhiteSpace(_livre.Editeur))
|
|
{
|
|
<div><dt>Éditeur</dt><dd>@_livre.Editeur</dd></div>
|
|
}
|
|
|
|
@* Tirets à l'affichage seulement : la valeur stockée reste nue. *@
|
|
@if (!string.IsNullOrWhiteSpace(_livre.Isbn))
|
|
{
|
|
<div><dt>ISBN</dt><dd class="code-isbn">@FormatageIsbn.Afficher(_livre.Isbn)</dd></div>
|
|
}
|
|
|
|
@* Comme dans la liste : le format n'apparaît que pour un ebook, le physique
|
|
étant le cas par défaut. *@
|
|
@if (_livre.Format == Format.Numerique)
|
|
{
|
|
<div><dt>Format</dt><dd>@Libelles.Format(_livre.Format)</dd></div>
|
|
}
|
|
|
|
@* Idem : « non précisé » est l'absence de réponse, pas une réponse. *@
|
|
@if (_livre.TypeDocument != TypeDocument.NonPrecise)
|
|
{
|
|
<div><dt>Type</dt><dd>@Libelles.TypeDocument(_livre.TypeDocument)</dd></div>
|
|
}
|
|
|
|
@if (_livre.Themes.Count > 0)
|
|
{
|
|
<div>
|
|
<dt>Thèmes</dt>
|
|
<dd class="etiquettes">
|
|
@foreach (var theme in _livre.Themes)
|
|
{
|
|
<span class="etiquette">@theme</span>
|
|
}
|
|
</dd>
|
|
</div>
|
|
}
|
|
|
|
@*
|
|
Même règle que le format et le type de document : la ligne ne s'affiche que
|
|
lorsqu'elle apprend quelque chose. « Nombre de pages : inconnu » sur presque
|
|
chaque fiche n'apprendrait rien et allongerait toutes les autres.
|
|
*@
|
|
@if (_livre.NombrePages is { } pages)
|
|
{
|
|
<div><dt>Pages</dt><dd>@pages</dd></div>
|
|
}
|
|
|
|
@if (!string.IsNullOrWhiteSpace(_livre.UrlNotice))
|
|
{
|
|
<div><dt>Source</dt><dd><a href="@_livre.UrlNotice" target="_blank" rel="noopener noreferrer">Voir la notice source</a></dd></div>
|
|
}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
@if (_correctionCouverture)
|
|
{
|
|
<div class="barre-recherche ligne-creation">
|
|
<input class="champ-saisie" type="url" @ref="_champUrlCouverture"
|
|
@bind="_urlCouverture" @bind:event="oninput"
|
|
@onkeydown="SurToucheCouverture"
|
|
placeholder="https://…" aria-label="Adresse de la couverture"
|
|
autocomplete="off" />
|
|
|
|
<button type="button" class="bouton bouton-principal"
|
|
disabled="@(_enregistrement || !Reseau.EnLigne)" title="@MotifBlocage"
|
|
@onclick="EnregistrerCouvertureAsync">
|
|
@(_enregistrement ? "Enregistrement…" : "Enregistrer")
|
|
</button>
|
|
|
|
<button type="button" class="bouton bouton-discret" disabled="@_enregistrement"
|
|
@onclick="() => _correctionCouverture = false">
|
|
Annuler
|
|
</button>
|
|
</div>
|
|
|
|
<p class="message-discret">
|
|
Collez l'adresse d'une image, puis Entrée. Vider le champ retire la couverture.
|
|
</p>
|
|
}
|
|
|
|
@* Hors « Éditer » volontairement : changer son statut est l'action la plus fréquente
|
|
de l'application, et elle ne touche qu'à MA lecture, jamais à la fiche commune. *@
|
|
<section class="bloc-statut">
|
|
<h2 class="titre-section">Statut de lecture <span class="champ-aide">(le vôtre)</span></h2>
|
|
<ChoixStatut Valeur="_livre.Statut"
|
|
ValeurChanged="ChangerStatutAsync"
|
|
Desactive="@(_statutEnCours || !Reseau.EnLigne)" />
|
|
@if (!Reseau.EnLigne)
|
|
{
|
|
<p class="message-discret">@EtatReseau.MotifHorsLigne</p>
|
|
}
|
|
</section>
|
|
|
|
<PretsLivre LivreId="Id" Format="_livre.Format"
|
|
PreteA="@_livre.PreteA" PreteDepuis="_livre.PreteDepuis"
|
|
OnChangement="RechargerAsync" />
|
|
|
|
<p class="message-discret">
|
|
Ajouté le @_livre.DateAjout.ToLocalTime().ToString("dd/MM/yyyy")
|
|
@if (!string.IsNullOrWhiteSpace(_livre.AjoutePar))
|
|
{
|
|
@($" par {_livre.AjoutePar}")
|
|
}
|
|
@* Information de traçabilité, jamais un filtre : la bibliothèque est commune. *@
|
|
</p>
|
|
|
|
<nav class="actions-flottantes">
|
|
@if (Reseau.EnLigne)
|
|
{
|
|
<a class="bouton bouton-principal" href="@($"livres/{Id}/edition")">Éditer</a>
|
|
}
|
|
else
|
|
{
|
|
<button type="button" class="bouton bouton-principal" disabled
|
|
title="@EtatReseau.MotifHorsLigne">Éditer</button>
|
|
}
|
|
</nav>
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public int Id { get; set; }
|
|
|
|
private LivreDto? _livre;
|
|
|
|
/// <summary>Places de ce livre dans des séries : la série, et son rang de lecture.</summary>
|
|
private IReadOnlyList<AppartenanceSerie> _appartenances = [];
|
|
|
|
private sealed record AppartenanceSerie(SerieDto Serie, int Rang);
|
|
|
|
/// <summary>
|
|
/// Cherche ce livre dans les séries.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Une seule lecture rend tout l'arbre, et elle est déjà en cache : chercher côté client
|
|
/// évite un point d'entrée « séries d'un livre » qui n'aurait servi qu'ici, et qui aurait
|
|
/// eu son propre instantané à tenir.
|
|
/// <para>
|
|
/// Le rang affiché est la <b>position dans l'ordre de lecture</b>, à partir de 1 — pas un
|
|
/// numéro de tome imprimé sur la couverture, que le modèle ne connaît pas.
|
|
/// </para>
|
|
/// </remarks>
|
|
private async Task ChargerAppartenancesAsync()
|
|
{
|
|
var series = await Api.ListerSeriesAsync();
|
|
|
|
_appartenances = series
|
|
.SelectMany(serie => serie.Elements
|
|
.Select((element, index) => (serie, element, index))
|
|
.Where(x => x.element.LivreId == Id)
|
|
.Select(x => new AppartenanceSerie(x.serie, x.index + 1)))
|
|
.ToList();
|
|
}
|
|
private EnregistrementLivre? _saisie;
|
|
private bool _introuvable;
|
|
private bool _enregistrement;
|
|
private bool _suppression;
|
|
private bool _confirmationSuppression;
|
|
private bool _statutEnCours;
|
|
private string? _erreur;
|
|
private int _livreCharge;
|
|
|
|
/// <summary>
|
|
/// Le mode se lit dans l'adresse, pas dans un champ : « /livres/12/edition » est partageable,
|
|
/// et le retour arrière du navigateur ramène naturellement à la consultation.
|
|
/// </summary>
|
|
private bool ModeEdition =>
|
|
Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath
|
|
.TrimEnd('/').EndsWith("/edition", StringComparison.OrdinalIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Les deux adresses mènent au même composant avec le même <c>Id</c> : passer de l'une à
|
|
/// l'autre ne change aucun paramètre, et le routeur ne provoque alors aucun nouveau rendu
|
|
/// (constaté : l'URL devenait « /edition » sans que l'écran bascule). On s'abonne donc au
|
|
/// changement d'adresse pour redessiner nous-mêmes.
|
|
/// </summary>
|
|
protected override void OnInitialized()
|
|
{
|
|
Navigation.LocationChanged += SurNavigation;
|
|
Reseau.Change += SurChangementReseau;
|
|
}
|
|
|
|
private void SurNavigation(object? emetteur, LocationChangedEventArgs e) =>
|
|
InvokeAsync(StateHasChanged);
|
|
|
|
/// <summary>Le réseau a basculé : la fiche se relit (serveur ou cache) et les actions suivent.</summary>
|
|
private void SurChangementReseau() => _ = InvokeAsync(async () =>
|
|
{
|
|
_livre = await Api.ObtenirAsync(Id) ?? _livre;
|
|
|
|
// On ne réécrit PAS le formulaire en cours d'édition : une coupure réseau au milieu
|
|
// d'une saisie effacerait ce que l'utilisateur vient de taper.
|
|
if (_livre is not null && !ModeEdition)
|
|
{
|
|
_saisie = Recopier(_livre);
|
|
}
|
|
|
|
StateHasChanged();
|
|
});
|
|
|
|
/// <summary>Raison affichable quand la fiche ne peut pas être modifiée, sinon <c>null</c>.</summary>
|
|
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
|
|
|
public void Dispose()
|
|
{
|
|
Navigation.LocationChanged -= SurNavigation;
|
|
Reseau.Change -= SurChangementReseau;
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
if (_livreCharge == Id && _livre is not null)
|
|
{
|
|
// Simple bascule consultation <-> édition : inutile de relire le livre.
|
|
return;
|
|
}
|
|
|
|
_livreCharge = Id;
|
|
_introuvable = false;
|
|
_livre = null;
|
|
_saisie = null;
|
|
|
|
try
|
|
{
|
|
_livre = await Api.ObtenirAsync(Id);
|
|
await ChargerAppartenancesAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_erreur = $"Chargement impossible : {ex.Message}";
|
|
return;
|
|
}
|
|
|
|
if (_livre is null)
|
|
{
|
|
_introuvable = true;
|
|
return;
|
|
}
|
|
|
|
_saisie = Recopier(_livre);
|
|
}
|
|
|
|
/// <summary>Formulaire d'édition alimenté depuis la fiche chargée.</summary>
|
|
/// <remarks>
|
|
/// Les auteurs sont repris tels que rattachés au livre. Le statut, lui, est celui de la
|
|
/// personne qui édite : enregistrer ne touche jamais à la lecture de quelqu'un d'autre.
|
|
/// </remarks>
|
|
private static EnregistrementLivre Recopier(LivreDto livre) => new()
|
|
{
|
|
Isbn = livre.Isbn,
|
|
Titre = livre.Titre,
|
|
Auteurs = livre.Auteurs.Select(a => new AuteurSaisi(a.Nom, a.Role)).ToList(),
|
|
Editeur = livre.Editeur,
|
|
Format = livre.Format,
|
|
TypeDocument = livre.TypeDocument,
|
|
NombrePages = livre.NombrePages,
|
|
Themes = livre.Themes.ToList(),
|
|
Statut = livre.Statut,
|
|
CoverUrl = livre.CoverUrl,
|
|
UrlNotice = livre.UrlNotice,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Recharge la fiche après un prêt ou un retour, <b>sans toucher au formulaire</b> : l'état
|
|
/// du prêt a changé, pas les champs que l'utilisateur est peut-être en train d'éditer.
|
|
/// </summary>
|
|
private async Task RechargerAsync() => _livre = await Api.ObtenirAsync(Id) ?? _livre;
|
|
|
|
/// <summary>Champ d'adresse de la couverture, ouvert depuis l'image cassée elle-même.</summary>
|
|
private bool _correctionCouverture;
|
|
|
|
private string _urlCouverture = string.Empty;
|
|
|
|
private ElementReference _champUrlCouverture;
|
|
|
|
private bool _focusCouverture;
|
|
|
|
private void OuvrirCorrectionCouverture()
|
|
{
|
|
_erreur = null;
|
|
_urlCouverture = _livre?.CoverUrl ?? string.Empty;
|
|
_correctionCouverture = true;
|
|
|
|
// ⚠️ Le focus est demandé UNE fois, au prochain rendu : le reprendre à chaque rendu
|
|
// l'arracherait pendant qu'on colle ou qu'on tape.
|
|
_focusCouverture = true;
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool premierRendu)
|
|
{
|
|
if (_focusCouverture)
|
|
{
|
|
_focusCouverture = false;
|
|
await _champUrlCouverture.FocusAsync();
|
|
}
|
|
}
|
|
|
|
private async Task SurToucheCouverture(KeyboardEventArgs e)
|
|
{
|
|
if (e.Key == "Enter")
|
|
{
|
|
await EnregistrerCouvertureAsync();
|
|
}
|
|
else if (e.Key is "Escape" or "Esc")
|
|
{
|
|
_correctionCouverture = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enregistre la seule adresse de couverture, sans passer par l'écran d'édition.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Même exception que le statut de lecture et les prêts : ce n'est pas « modifier la fiche »,
|
|
/// c'est réparer ce qu'on a sous les yeux. La saisie du formulaire d'édition est mise à jour
|
|
/// dans la foulée, sinon un passage ultérieur en édition réécrirait l'ancienne adresse.
|
|
/// </remarks>
|
|
private async Task EnregistrerCouvertureAsync()
|
|
{
|
|
if (_saisie is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var url = _urlCouverture.Trim();
|
|
_saisie.CoverUrl = string.IsNullOrEmpty(url) ? null : url;
|
|
|
|
_enregistrement = true;
|
|
_erreur = null;
|
|
|
|
var resultat = await Api.ModifierAsync(Id, _saisie);
|
|
|
|
_enregistrement = false;
|
|
|
|
if (!resultat.EstOk)
|
|
{
|
|
_erreur = resultat.Erreur;
|
|
return;
|
|
}
|
|
|
|
_livre = resultat.Valeur;
|
|
_saisie = Recopier(_livre!);
|
|
_correctionCouverture = false;
|
|
}
|
|
|
|
private async Task ChangerStatutAsync(Statut? statut)
|
|
{
|
|
_statutEnCours = true;
|
|
_erreur = null;
|
|
|
|
var resultat = await Api.ChangerStatutAsync(Id, statut);
|
|
|
|
_statutEnCours = false;
|
|
|
|
if (!resultat.EstOk)
|
|
{
|
|
_erreur = resultat.Erreur;
|
|
return;
|
|
}
|
|
|
|
_livre = resultat.Valeur;
|
|
|
|
// Le formulaire d'édition doit repartir du statut à jour, sinon un passage en édition
|
|
// réécrirait l'ancienne valeur au premier enregistrement.
|
|
if (_livre is not null)
|
|
{
|
|
_saisie = Recopier(_livre);
|
|
}
|
|
}
|
|
|
|
private async Task EnregistrerAsync()
|
|
{
|
|
_enregistrement = true;
|
|
_erreur = null;
|
|
|
|
var resultat = await Api.ModifierAsync(Id, _saisie!);
|
|
|
|
_enregistrement = false;
|
|
|
|
if (!resultat.EstOk)
|
|
{
|
|
_erreur = resultat.Erreur;
|
|
return;
|
|
}
|
|
|
|
// Retour à la consultation, pas au catalogue : on voit le résultat de sa modification.
|
|
_livre = resultat.Valeur;
|
|
_saisie = Recopier(_livre!);
|
|
RetourFiche();
|
|
}
|
|
|
|
private async Task SupprimerAsync()
|
|
{
|
|
_suppression = true;
|
|
var erreur = await Api.SupprimerAsync(Id);
|
|
_suppression = false;
|
|
|
|
if (erreur is not null)
|
|
{
|
|
_erreur = erreur;
|
|
_confirmationSuppression = false;
|
|
return;
|
|
}
|
|
|
|
Navigation.NavigateTo("/");
|
|
}
|
|
|
|
private void RetourFiche()
|
|
{
|
|
_confirmationSuppression = false;
|
|
Navigation.NavigateTo($"livres/{Id}");
|
|
}
|
|
}
|