Fusionner la gestion des prets
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
@inject ServiceLivresApi Api
|
||||
|
||||
@*
|
||||
Prêts d'un livre : l'état courant, l'action qui s'impose, puis l'historique.
|
||||
|
||||
Mobile d'abord : une seule colonne, un seul bouton visible à la fois. Prêter et rendre sont
|
||||
les deux gestes du quotidien — ils sont en haut, l'historique se déroule dessous.
|
||||
*@
|
||||
<section class="bloc-prets">
|
||||
<h2 class="titre-section">Prêt</h2>
|
||||
|
||||
@if (Format == MaBibli.Shared.Entites.Format.Numerique)
|
||||
{
|
||||
@* CLAUDE.md : un ebook n'est qu'une fiche, il n'y a pas d'exemplaire à confier.
|
||||
L'API le refuse aussi — ici on évite simplement de proposer un geste impossible. *@
|
||||
<p class="message-discret">
|
||||
Un livre numérique ne se prête pas : il n'y a pas d'exemplaire à confier.
|
||||
</p>
|
||||
}
|
||||
else if (_prets is null)
|
||||
{
|
||||
<p class="message-discret">Chargement…</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (_erreur is not null)
|
||||
{
|
||||
<p class="message-erreur" role="alert">@_erreur</p>
|
||||
}
|
||||
|
||||
@if (EnCours is { } pret)
|
||||
{
|
||||
<p class="bandeau-pret">
|
||||
<span class="etiquette etiquette-prete">Prêté</span>
|
||||
à <strong>@pret.Emprunteur</strong>
|
||||
depuis le @Jour(pret.DatePret)
|
||||
<span class="pret-duree">(@Duree(pret.JoursEcoules))</span>
|
||||
</p>
|
||||
|
||||
<div class="actions-formulaire">
|
||||
<button type="button" class="bouton bouton-principal" disabled="@_occupe"
|
||||
@onclick="RendreAsync">
|
||||
@(_occupe ? "Enregistrement…" : "Marquer comme rendu")
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else if (!_formulaireOuvert)
|
||||
{
|
||||
<p class="message-discret">Ce livre est à la maison.</p>
|
||||
|
||||
<div class="actions-formulaire">
|
||||
<button type="button" class="bouton bouton-principal" @onclick="OuvrirFormulaire">
|
||||
Prêter ce livre
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Emprunteur en texte libre : ce sont des gens du dehors, sans compte YunoHost. *@
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">Prêté à <span class="obligatoire" aria-hidden="true">*</span></span>
|
||||
<input class="champ-saisie" type="text" @bind="_emprunteur" @bind:event="oninput"
|
||||
@onkeydown="SurTouche" placeholder="Prénom, nom…" autocomplete="off" />
|
||||
</label>
|
||||
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">
|
||||
Depuis le<span class="champ-aide"> (on note souvent un prêt après coup)</span>
|
||||
</span>
|
||||
<input class="champ-saisie" type="date" @bind="_datePret"
|
||||
max="@DateTime.Today.ToString("yyyy-MM-dd")" />
|
||||
</label>
|
||||
|
||||
<div class="actions-formulaire">
|
||||
<button type="button" class="bouton bouton-principal"
|
||||
disabled="@(_occupe || string.IsNullOrWhiteSpace(_emprunteur))"
|
||||
@onclick="PreterAsync">
|
||||
@(_occupe ? "Enregistrement…" : "Prêter")
|
||||
</button>
|
||||
<button type="button" class="bouton bouton-discret" disabled="@_occupe"
|
||||
@onclick="() => _formulaireOuvert = false">
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Historique.Count > 0)
|
||||
{
|
||||
@* L'historique est la raison d'être de la table : un prêt rendu ne disparaît pas. *@
|
||||
<h3 class="titre-sous-section">Prêts passés</h3>
|
||||
|
||||
<ul class="liste-historique">
|
||||
@foreach (var passe in Historique)
|
||||
{
|
||||
<li @key="passe.Id">
|
||||
<strong>@passe.Emprunteur</strong>
|
||||
<span class="historique-dates">
|
||||
du @Jour(passe.DatePret) au @Jour(passe.DateRetour!.Value)
|
||||
— @Duree(passe.JoursEcoules)
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
else if (EnCours is null)
|
||||
{
|
||||
<p class="message-discret">Jamais prêté.</p>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public int LivreId { get; set; }
|
||||
|
||||
/// <summary>Format du livre : un ebook ne se prête pas.</summary>
|
||||
[Parameter] public Format Format { get; set; }
|
||||
|
||||
/// <summary>Prévient la page parente qu'un prêt a changé, pour qu'elle rafraîchisse sa fiche.</summary>
|
||||
[Parameter] public EventCallback OnChangement { get; set; }
|
||||
|
||||
private IReadOnlyList<PretDto>? _prets;
|
||||
private bool _formulaireOuvert;
|
||||
private bool _occupe;
|
||||
private string _emprunteur = string.Empty;
|
||||
private DateTime _datePret = DateTime.Today;
|
||||
private string? _erreur;
|
||||
private int _livreCharge;
|
||||
|
||||
private PretDto? EnCours => _prets?.FirstOrDefault(p => p.EstEnCours);
|
||||
|
||||
private IReadOnlyList<PretDto> Historique =>
|
||||
_prets?.Where(p => !p.EstEnCours).ToList() ?? [];
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_livreCharge != LivreId)
|
||||
{
|
||||
_livreCharge = LivreId;
|
||||
_prets = null;
|
||||
await ChargerAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChargerAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_prets = await Api.ListerPretsLivreAsync(LivreId) ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_erreur = $"Impossible de charger les prêts : {ex.Message}";
|
||||
_prets = [];
|
||||
}
|
||||
}
|
||||
|
||||
private void OuvrirFormulaire()
|
||||
{
|
||||
_emprunteur = string.Empty;
|
||||
_datePret = DateTime.Today;
|
||||
_erreur = null;
|
||||
_formulaireOuvert = true;
|
||||
}
|
||||
|
||||
private async Task SurTouche(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_emprunteur))
|
||||
{
|
||||
await PreterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PreterAsync()
|
||||
{
|
||||
_occupe = true;
|
||||
_erreur = null;
|
||||
|
||||
// La date n'est transmise que si elle diffère d'aujourd'hui : sinon on laisse le serveur
|
||||
// horodater à l'instant présent, plus précis que « minuit ce matin ».
|
||||
var date = _datePret.Date == DateTime.Today ? (DateTime?)null : _datePret;
|
||||
var resultat = await Api.PreterAsync(LivreId, _emprunteur.Trim(), date);
|
||||
|
||||
_occupe = false;
|
||||
|
||||
if (!resultat.EstOk)
|
||||
{
|
||||
_erreur = resultat.Erreur;
|
||||
return;
|
||||
}
|
||||
|
||||
_formulaireOuvert = false;
|
||||
await ChargerAsync();
|
||||
await OnChangement.InvokeAsync();
|
||||
}
|
||||
|
||||
private async Task RendreAsync()
|
||||
{
|
||||
_occupe = true;
|
||||
_erreur = null;
|
||||
|
||||
var resultat = await Api.RendreAsync(EnCours!.Id);
|
||||
|
||||
_occupe = false;
|
||||
|
||||
if (!resultat.EstOk)
|
||||
{
|
||||
_erreur = resultat.Erreur;
|
||||
|
||||
// Le prêt a peut-être été clos ailleurs (autre membre du foyer, autre onglet) :
|
||||
// recharger vaut mieux que laisser un bouton qui ne peut plus rien faire.
|
||||
await ChargerAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await ChargerAsync();
|
||||
await OnChangement.InvokeAsync();
|
||||
}
|
||||
|
||||
private static string Jour(DateTime instant) =>
|
||||
instant.ToLocalTime().ToString("dd/MM/yyyy");
|
||||
|
||||
/// <summary>Durée en clair : « 3 jours », « 2 mois ». Le nombre de jours exact ne parle plus au-delà.</summary>
|
||||
internal static string Duree(int jours) => jours switch
|
||||
{
|
||||
0 => "aujourd'hui",
|
||||
1 => "1 jour",
|
||||
< 31 => $"{jours} jours",
|
||||
< 365 => $"{jours / 30} mois",
|
||||
< 730 => "plus d'un an",
|
||||
_ => $"plus de {jours / 365} ans",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user