Le piege que CLAUDE.md signale : en Blazor WebAssembly le code tourne dans le navigateur alors que SQLite vit sur le serveur, et le service worker ne met en cache que les assets. Sans travail explicite, l'application demarre hors-ligne et affiche une bibliotheque vide. - js/cache-hors-ligne.js : instantanes JSON dans IndexedDB, et surveillance des bascules online/offline. Aucune logique metier. - CacheHorsLigne / EtatReseau : lecture-ecriture des instantanes, et etat reseau combinant navigator.onLine (fiable seulement par sa negation) avec le sort reel des appels HTTP. - ServiceLivresApi : les lectures retombent sur le cache, les ecritures sont refusees. Le catalogue entier est memorise, pas les reponses filtrees : c'est ce qui rend la recherche hors-ligne possible sur tout le fonds. - FiltreLivresLocal : pendant navigateur de FiltreLivres, avec un test qui confronte les deux implementations sur les memes donnees. - Interface : pastille et bandeau d'etat avec la date de synchronisation, et actions d'ecriture desactivees avec leur raison plutot que boutons morts. - js/mise-a-jour.js : les empreintes WASM etant desactivees, le service worker est le seul cache-busting du projet. L'enregistrement journalise desormais ses echecs, et un bandeau propose la nouvelle version sans attendre la fermeture de tous les onglets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
4.2 KiB
Plaintext
128 lines
4.2 KiB
Plaintext
@page "/prets"
|
|
@inject ServiceLivresApi Api
|
|
@inject EtatReseau Reseau
|
|
@implements IDisposable
|
|
|
|
@*
|
|
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)
|
|
{
|
|
@* Hors-ligne, une liste vide peut aussi vouloir dire « rien n'a encore été enregistré ici » :
|
|
on ne l'affirme comme un fait que si l'information vient bien d'une synchronisation. *@
|
|
<p class="message-discret">
|
|
@(Reseau.EnLigne || Reseau.DerniereSynchro is not null
|
|
? "Tous les livres sont à la maison."
|
|
: "Aucune donnée enregistrée sur cet appareil : impossible de dire ce qui est prêté.")
|
|
</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" Agrandissable="true" />
|
|
|
|
<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 || !Reseau.EnLigne)"
|
|
title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
|
|
@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 void OnInitialized() => Reseau.Change += SurChangementReseau;
|
|
|
|
private void SurChangementReseau() => _ = InvokeAsync(async () =>
|
|
{
|
|
await ChargerAsync();
|
|
StateHasChanged();
|
|
});
|
|
|
|
public void Dispose() => Reseau.Change -= SurChangementReseau;
|
|
|
|
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();
|
|
}
|
|
}
|