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>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
@page "/prets"
|
||||
@inject ServiceLivresApi Api
|
||||
|
||||
@*
|
||||
Ce qui n'est pas à la maison. C'est l'usage principal des prêts : savoir d'un coup d'œil
|
||||
quels livres sont dehors, et chez qui.
|
||||
|
||||
La liste est commune à tout le foyer, sans filtre par utilisateur : n'importe qui doit
|
||||
pouvoir constater qu'un livre manque, et noter son retour quand il le récupère.
|
||||
*@
|
||||
|
||||
<PageTitle>MaBibli — prêts en cours</PageTitle>
|
||||
|
||||
<h1 class="titre-page">Prêts en cours</h1>
|
||||
|
||||
@if (_erreur is not null)
|
||||
{
|
||||
<p class="message-erreur" role="alert">@_erreur</p>
|
||||
}
|
||||
|
||||
@if (_prets is null)
|
||||
{
|
||||
<p class="message-discret">Chargement…</p>
|
||||
}
|
||||
else if (_prets.Count == 0)
|
||||
{
|
||||
<p class="message-discret">Tous les livres sont à la maison.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="compteur">@_prets.Count livre@(_prets.Count > 1 ? "s" : "") dehors</p>
|
||||
|
||||
<ul class="liste-livres">
|
||||
@foreach (var pret in _prets)
|
||||
{
|
||||
<li class="carte-livre" @key="pret.Id">
|
||||
<Couverture Url="@pret.LivreCoverUrl" Titre="@pret.LivreTitre" Classe="couverture-petite" />
|
||||
|
||||
<div class="carte-corps">
|
||||
<a class="carte-titre" href="@($"livres/{pret.LivreId}")">@pret.LivreTitre</a>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(pret.LivreAuteurs))
|
||||
{
|
||||
<p class="carte-auteur">@pret.LivreAuteurs</p>
|
||||
}
|
||||
|
||||
<p class="carte-details">
|
||||
<span class="etiquette etiquette-prete">@pret.Emprunteur</span>
|
||||
<span>depuis le @pret.DatePret.ToLocalTime().ToString("dd/MM/yyyy")</span>
|
||||
<span class="pret-duree">@PretsLivre.Duree(pret.JoursEcoules)</span>
|
||||
</p>
|
||||
|
||||
@* Le retour se note ici : on récupère un livre, on ouvre la liste des prêts,
|
||||
on coche. Passer par la fiche du livre serait un détour. *@
|
||||
<button type="button" class="bouton bouton-discret bouton-compact"
|
||||
disabled="@(_enCours == pret.Id)"
|
||||
@onclick="() => RendreAsync(pret)">
|
||||
@(_enCours == pret.Id ? "Enregistrement…" : "Rendu")
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
<nav class="actions-flottantes">
|
||||
<a class="bouton bouton-discret" href="">Catalogue</a>
|
||||
</nav>
|
||||
|
||||
@code {
|
||||
private IReadOnlyList<PretDto>? _prets;
|
||||
private string? _erreur;
|
||||
private int? _enCours;
|
||||
|
||||
protected override Task OnInitializedAsync() => ChargerAsync();
|
||||
|
||||
private async Task ChargerAsync()
|
||||
{
|
||||
_erreur = null;
|
||||
|
||||
try
|
||||
{
|
||||
_prets = await Api.ListerPretsEnCoursAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_erreur = $"Impossible de charger les prêts : {ex.Message}";
|
||||
_prets = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RendreAsync(PretDto pret)
|
||||
{
|
||||
_enCours = pret.Id;
|
||||
var resultat = await Api.RendreAsync(pret.Id);
|
||||
_enCours = null;
|
||||
|
||||
if (!resultat.EstOk)
|
||||
{
|
||||
_erreur = resultat.Erreur;
|
||||
}
|
||||
|
||||
// Rechargement complet plutôt que retrait local : quelqu'un d'autre du foyer a pu rendre
|
||||
// ou prêter un livre entre-temps, et cette vue est justement celle qu'on garde ouverte.
|
||||
await ChargerAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user