@inject ServiceLivresApi Api @inject EtatReseau Reseau @implements IDisposable @* 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. *@

Prêt

@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. *@

Un livre numérique ne se prête pas : il n'y a pas d'exemplaire à confier.

} else if (!Reseau.EnLigne) { @* L'historique des prêts n'est pas mis en cache : une requête par livre pour une information rarement consultée. En revanche l'état COURANT vient de la fiche, donc de l'instantané du catalogue — c'est lui qui répond à « où est ce livre ? », la seule question qui se pose vraiment sans réseau, devant l'étagère. Afficher une liste vide ferait croire à « jamais prêté » : on dit ce qu'on sait, et on dit ce qu'on ignore. *@ @if (PreteA is { } absent) {

Prêté à @absent @if (PreteDepuis is { } depuis) { @($" depuis le {Jour(depuis)}") }

} else {

Ce livre était à la maison à la dernière synchronisation.

}

Hors ligne : l'historique des prêts n'est pas consultable, et prêter ou marquer un retour demande le réseau.

} else if (_prets is null) {

Chargement…

} else { @if (_erreur is not null) { } @if (EnCours is { } pret) {

Prêté à @pret.Emprunteur depuis le @Jour(pret.DatePret) (@Duree(pret.JoursEcoules))

} else if (!_formulaireOuvert) {

Ce livre est à la maison.

} else { @* Emprunteur en texte libre : ce sont des gens du dehors, sans compte YunoHost. *@
} @if (Historique.Count > 0) { @* L'historique est la raison d'être de la table : un prêt rendu ne disparaît pas. *@

Prêts passés

} else if (EnCours is null) {

Jamais prêté.

} }
@code { [Parameter, EditorRequired] public int LivreId { get; set; } /// Format du livre : un ebook ne se prête pas. [Parameter] public Format Format { get; set; } /// /// Emprunteur actuel d'après la fiche. Sert hors-ligne, où l'historique n'est pas /// consultable mais où l'état courant, lui, vient de l'instantané du catalogue. /// [Parameter] public string? PreteA { get; set; } /// Date du prêt en cours, même origine que . [Parameter] public DateTime? PreteDepuis { get; set; } /// Prévient la page parente qu'un prêt a changé, pour qu'elle rafraîchisse sa fiche. [Parameter] public EventCallback OnChangement { get; set; } private IReadOnlyList? _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 Historique => _prets?.Where(p => !p.EstEnCours).ToList() ?? []; protected override void OnInitialized() => Reseau.Change += SurChangementReseau; /// Au retour du réseau, l'historique devient consultable : on va le chercher. private void SurChangementReseau() => _ = InvokeAsync(async () => { if (Reseau.EnLigne) { await ChargerAsync(); } StateHasChanged(); }); public void Dispose() => Reseau.Change -= SurChangementReseau; protected override async Task OnParametersSetAsync() { if (_livreCharge != LivreId) { _livreCharge = LivreId; _prets = null; await ChargerAsync(); } } private async Task ChargerAsync() { if (!Reseau.EnLigne) { return; // L'historique n'est pas en cache : rien à demander, l'affichage le dit. } 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"); /// Durée en clair : « 3 jours », « 2 mois ». Le nombre de jours exact ne parle plus au-delà. 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", }; }