Files
mabibli/MaBibli.Client/Pages/EditionLivre.razor
T
mathieuandClaude Opus 5 2a3dfb88de Gerer les prets : API, ecrans et contrainte en base
L'entite Pret existait depuis le squelette sans jamais etre exploitee. Cette
phase la met en service de bout en bout.

API
- GET  /api/prets/en-cours       ce qui n'est pas a la maison, du plus ancien
                                 au plus recent : on cherche le livre oublie,
                                 pas celui prete hier
- POST /api/livres/{id}/prets    preter
- GET  /api/livres/{id}/prets    historique complet, du plus recent au plus
                                 ancien
- POST /api/prets/{id}/retour    clore le pret sans le supprimer

Un livre deja sorti ne peut pas etre prete une seconde fois. Le service le
verifie et nomme celui qui l'a deja, mais entre sa verification et l'insertion
il reste une fenetre : un index unique PARTIEL (LivreId WHERE DateRetour IS
NULL) la ferme, tout en laissant l'historique accumuler autant de prets clos
que necessaire sur le meme livre.

Les ebooks sont refuses cote API, pas seulement grises dans l'interface : une
fiche n'a pas d'exemplaire a confier.

Les prets sont COMMUNS au foyer, symetrique inverse du statut de lecture. Le
service ne recoit meme pas d'identite, pour qu'on ne puisse pas s'en servir
par inadvertance.

Interface, pensee mobile d'abord
- ecran « Prets en cours » avec bouton « Rendu » a meme la liste
- bloc pret sur la fiche d'un livre : etat courant, action, puis historique
- etiquette « Prete a X » dans le catalogue
- la barre d'actions passe sur deux lignes plutot que de comprimer ses
  libelles maintenant qu'elle compte quatre entrees

Toutes les dates sont en UTC ; le client convertit la date locale du
<input type="date"> avant l'envoi, faute de quoi le pret se decalerait d'un
jour pour la moitie du globe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 02:45:01 +02:00

163 lines
4.9 KiB
Plaintext

@page "/livres/{Id:int}"
@inject ServiceLivresApi Api
@inject NavigationManager Navigation
<PageTitle>MaBibli — fiche du livre</PageTitle>
<h1 class="titre-page">Fiche du livre</h1>
@if (_introuvable)
{
<p class="message-erreur" role="alert">Ce livre n'existe pas (ou plus).</p>
<a class="bouton bouton-discret" href="">Retour au catalogue</a>
}
else if (_saisie is null)
{
<p class="message-discret">Chargement…</p>
}
else
{
<FormulaireLivre Saisie="_saisie"
LibelleValidation="Enregistrer les modifications"
Erreur="@_erreur"
EnCours="_enregistrement"
OnValider="EnregistrerAsync"
OnAnnuler="Retour" />
@if (_livre is not null)
{
<PretsLivre LivreId="Id" Format="_livre.Format" 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>
}
<section class="zone-danger">
@if (!_confirmationSuppression)
{
<button type="button" class="bouton bouton-danger" @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>
}
@code {
[Parameter] public int Id { get; set; }
private LivreDto? _livre;
private EnregistrementLivre? _saisie;
private bool _introuvable;
private bool _enregistrement;
private bool _suppression;
private bool _confirmationSuppression;
private string? _erreur;
protected override async Task OnParametersSetAsync()
{
_introuvable = false;
_saisie = null;
try
{
_livre = await Api.ObtenirAsync(Id);
}
catch (Exception ex)
{
_erreur = $"Chargement impossible : {ex.Message}";
return;
}
if (_livre is null)
{
_introuvable = true;
return;
}
_saisie = new EnregistrementLivre
{
Isbn = _livre.Isbn,
Titre = _livre.Titre,
// 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.
Auteurs = _livre.Auteurs.Select(a => a.Nom).ToList(),
Editeur = _livre.Editeur,
Format = _livre.Format,
Statut = _livre.Statut,
CoverUrl = _livre.CoverUrl,
};
}
/// <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);
private async Task EnregistrerAsync()
{
_enregistrement = true;
_erreur = null;
var resultat = await Api.ModifierAsync(Id, _saisie!);
_enregistrement = false;
if (!resultat.EstOk)
{
_erreur = resultat.Erreur;
return;
}
Navigation.NavigateTo("/");
}
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 Retour() => Navigation.NavigateTo("/");
}