La liste d'envies vit dans une table séparée (LivreSouhaite) plutôt que dans un statut de plus sur Livre : un livre souhaité n'est pas possédé, et le loger dans Livres l'aurait fait entrer dans le catalogue, les compteurs et les prêts, au prix d'un « et qui n'est pas souhaité » à répéter dans chaque lecture. La portée est personnelle, comme le statut de lecture — mais ici Utilisateur est une vraie frontière : toute lecture filtre dessus. Bibliographie : SRU BnF interrogé par bib.author, vérifié le 2026-08-18. Deux filtres mesurés sur des réponses réelles sont indispensables — le type de document (l'index mêle livres audio, jeux et spectacles) et surtout l'auteur réel de la notice, « all » rapprochant les mots sur l'ensemble des auteurs : « Émile Zola » remonte sinon toute l'œuvre de sa fille Denise Le Blond-Zola. Les rééditions sont regroupées par clé d'œuvre (183 notices Werber -> 51 œuvres). Le rapprochement avec l'étagère se fait par titre, pas par ISBN, qui désigne une édition et non une œuvre ; ses limites sont dites à l'écran. Export en deux formats, tous deux du texte sans dépendance : .txt groupé par auteur pour la librairie, .csv à séparateur point-virgule et BOM pour le tableur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
246 lines
9.3 KiB
Plaintext
246 lines
9.3 KiB
Plaintext
@page "/auteurs/{AuteurId:int}/bibliographie"
|
|
@inject ServiceLivresApi Api
|
|
|
|
<PageTitle>MaBibli — bibliographie</PageTitle>
|
|
|
|
@if (_bibliographie is null && _erreur is null)
|
|
{
|
|
<h1 class="titre-page">Bibliographie</h1>
|
|
<p class="message-discret">Interrogation de la BnF…</p>
|
|
}
|
|
else if (_erreur is not null)
|
|
{
|
|
<h1 class="titre-page">Bibliographie</h1>
|
|
<p class="message-erreur" role="alert">@_erreur</p>
|
|
}
|
|
else if (_bibliographie is { } biblio)
|
|
{
|
|
<h1 class="titre-page">@biblio.Auteur.Nom</h1>
|
|
|
|
@if (biblio.Avertissement is not null)
|
|
{
|
|
<p class="message-avertissement" role="alert">@biblio.Avertissement</p>
|
|
}
|
|
|
|
@if (biblio.Oeuvres.Count == 0)
|
|
{
|
|
<p class="message-discret">
|
|
La BnF ne connaît aucun livre de cet auteur. C'est le cas attendu pour les auteurs
|
|
étrangers : le dépôt légal français ne couvre que l'édition francophone.
|
|
</p>
|
|
}
|
|
else
|
|
{
|
|
@* Ces compteurs disent à l'utilisateur CE QU'IL REGARDE. Sur un auteur très réédité,
|
|
la liste n'est qu'un extrait : le taire ferait croire qu'un livre absent n'existe pas. *@
|
|
<p class="compteur">
|
|
@biblio.Oeuvres.Count œuvre@(biblio.Oeuvres.Count > 1 ? "s" : "")
|
|
— @_possedees déjà chez vous
|
|
</p>
|
|
|
|
@* Deux phrases distinctes plutôt qu'une seule à trous : enchaîner des fragments
|
|
conditionnels dans une même phrase produit une ponctuation bancale dès qu'un des
|
|
fragments manque (« exister. .34 notices ont été écartées »). *@
|
|
<p class="message-discret">
|
|
@if (biblio.Tronquee)
|
|
{
|
|
<text>
|
|
D'après le catalogue de la BnF, à partir de @biblio.NoticesLues notices
|
|
sur les <strong>@biblio.NoticesAnnoncees</strong> qu'elle annonce :
|
|
<strong>cette liste est un extrait</strong>, classé par pertinence.
|
|
Un livre absent d'ici peut très bien exister.
|
|
</text>
|
|
}
|
|
else
|
|
{
|
|
<text>
|
|
D'après le catalogue de la BnF, sur
|
|
@biblio.NoticesLues notice@(biblio.NoticesLues > 1 ? "s" : "").
|
|
</text>
|
|
}
|
|
</p>
|
|
|
|
@if (biblio.NoticesEcartees > 0)
|
|
{
|
|
<p class="message-discret">
|
|
@biblio.NoticesEcartees notice@(biblio.NoticesEcartees > 1 ? "s ont" : " a")
|
|
été écartée@(biblio.NoticesEcartees > 1 ? "s" : "") : autre support (livre audio,
|
|
jeu) ou ouvrage signé de quelqu'un d'autre.
|
|
</p>
|
|
}
|
|
|
|
@if (_message is not null)
|
|
{
|
|
<p class="message-erreur" role="alert">@_message</p>
|
|
}
|
|
|
|
@* Filtre sur une seule question : « qu'est-ce qui me manque ? ». Les livres possédés ne
|
|
sont jamais masqués par défaut — le grisage est l'information principale de l'écran. *@
|
|
<div class="filtres">
|
|
<div class="segments" role="group" aria-label="Filtrer la bibliographie">
|
|
<button type="button" class="segment @(_seulementManquants ? null : "segment-actif")"
|
|
@onclick="() => _seulementManquants = false">Tout</button>
|
|
<button type="button" class="segment @(_seulementManquants ? "segment-actif" : null)"
|
|
@onclick="() => _seulementManquants = true">Ce qui me manque</button>
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="liste-bibliographie">
|
|
@foreach (var oeuvre in Affichees(biblio))
|
|
{
|
|
@* La classe « oeuvre-possedee » grise la ligne : le livre est déjà sur l'étagère. *@
|
|
<li class="carte-oeuvre @(oeuvre.Possede ? "oeuvre-possedee" : null)" @key="oeuvre.Titre">
|
|
<div class="carte-corps">
|
|
<p class="carte-titre">
|
|
@if (oeuvre is { Possede: true, LivreId: { } livreId })
|
|
{
|
|
<a class="lien-auteur" href="@($"livres/{livreId}")">@oeuvre.Titre</a>
|
|
}
|
|
else
|
|
{
|
|
@oeuvre.Titre
|
|
}
|
|
</p>
|
|
|
|
<p class="carte-details">
|
|
@if (!string.IsNullOrWhiteSpace(oeuvre.Annee))
|
|
{
|
|
<span>@oeuvre.Annee</span>
|
|
}
|
|
@if (!string.IsNullOrWhiteSpace(oeuvre.Editeur))
|
|
{
|
|
<span>@oeuvre.Editeur</span>
|
|
}
|
|
@if (oeuvre.NombreEditions > 1)
|
|
{
|
|
<span class="etiquette">@oeuvre.NombreEditions éditions</span>
|
|
}
|
|
|
|
@if (oeuvre.Possede)
|
|
{
|
|
<span class="etiquette etiquette-possede">Déjà chez vous</span>
|
|
}
|
|
else if (oeuvre.Souhaite)
|
|
{
|
|
<span class="etiquette etiquette-souhaite">Dans vos envies</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
@if (oeuvre.ADecouvrir)
|
|
{
|
|
<button type="button" class="bouton bouton-compact bouton-principal"
|
|
disabled="@_enCours" @onclick="() => SouhaiterAsync(oeuvre)"
|
|
aria-label="@($"Ajouter « {oeuvre.Titre} » à ma liste d'envies")">
|
|
Je le veux
|
|
</button>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
|
|
@* Le rapprochement est fait par titre, et il rate des choses (voir CleOeuvre). Le dire
|
|
franchement vaut mieux que laisser l'utilisateur découvrir seul qu'un livre qu'il
|
|
possède n'est pas grisé. *@
|
|
<p class="message-discret">
|
|
Le rapprochement avec votre bibliothèque se fait <strong>par titre</strong>. Un livre
|
|
que vous possédez sous un titre différent (traduction, intégrale, édition retitrée)
|
|
n'apparaîtra donc pas comme possédé. Rien n'est masqué : à vous de trancher.
|
|
</p>
|
|
}
|
|
}
|
|
|
|
<nav class="actions-flottantes">
|
|
<a class="bouton bouton-discret" href="auteurs">Retour aux auteurs</a>
|
|
@if (_bibliographie is { } b)
|
|
{
|
|
<a class="bouton bouton-discret" href="@($"?auteur={b.Auteur.Id}")">Ses livres chez vous</a>
|
|
}
|
|
<a class="bouton bouton-discret" href="souhaits">Ma liste d'envies</a>
|
|
</nav>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public int AuteurId { get; set; }
|
|
|
|
private BibliographieDto? _bibliographie;
|
|
private bool _seulementManquants;
|
|
private bool _enCours;
|
|
private string? _erreur;
|
|
private string? _message;
|
|
|
|
private int _possedees;
|
|
|
|
protected override async Task OnParametersSetAsync() => await ChargerAsync();
|
|
|
|
private IEnumerable<OeuvreBibliographie> Affichees(BibliographieDto biblio) =>
|
|
_seulementManquants ? biblio.Oeuvres.Where(o => o.ADecouvrir) : biblio.Oeuvres;
|
|
|
|
private async Task ChargerAsync()
|
|
{
|
|
_erreur = null;
|
|
|
|
try
|
|
{
|
|
_bibliographie = await Api.ObtenirBibliographieAsync(AuteurId);
|
|
|
|
if (_bibliographie is null)
|
|
{
|
|
_erreur = "Cet auteur n'existe pas dans votre bibliothèque.";
|
|
return;
|
|
}
|
|
|
|
_possedees = _bibliographie.Oeuvres.Count(o => o.Possede);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_erreur = $"Impossible d'obtenir la bibliographie : {ex.Message}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ajoute une œuvre à la liste d'envies, en emportant ce que la BnF en sait.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// L'auteur enregistré est celui de l'écran, pas celui de la notice : c'est lui que
|
|
/// l'utilisateur a en tête, et c'est sous ce nom qu'il retrouvera l'envie dans l'export.
|
|
/// <para>
|
|
/// Après l'ajout, la bibliographie est <b>relue</b> plutôt que corrigée en mémoire : c'est
|
|
/// le serveur qui sait rapprocher l'envie de l'œuvre, et refaire ce calcul ici le
|
|
/// dupliquerait — avec le risque qu'il diverge.
|
|
/// </para>
|
|
/// </remarks>
|
|
private async Task SouhaiterAsync(OeuvreBibliographie oeuvre)
|
|
{
|
|
if (_bibliographie is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_enCours = true;
|
|
_message = null;
|
|
|
|
var resultat = await Api.AjouterSouhaitAsync(new EnregistrementSouhait
|
|
{
|
|
Titre = oeuvre.Titre,
|
|
Auteur = _bibliographie.Auteur.Nom,
|
|
Editeur = oeuvre.Editeur,
|
|
Annee = oeuvre.Annee,
|
|
|
|
// L'ISBN de la BnF est celui d'UNE édition relevée, souvent à 10 chiffres et parfois
|
|
// épuisée. Il est repris tel quel : il aide à commander, sans engager le choix.
|
|
Isbn = oeuvre.Isbn,
|
|
});
|
|
|
|
_enCours = false;
|
|
|
|
if (!resultat.EstOk)
|
|
{
|
|
_message = resultat.Erreur;
|
|
return;
|
|
}
|
|
|
|
await ChargerAsync();
|
|
}
|
|
}
|