Files
mabibli/MaBibli.Client/Pages/Auteurs.razor
T
mathieuandClaude Opus 5 8b8fe3be6e Ajouter la liste d'envies personnelle, son export et la bibliographie par auteur
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>
2026-08-18 13:59:06 +02:00

135 lines
4.8 KiB
Plaintext

@page "/auteurs"
@inject ServiceLivresApi Api
<PageTitle>MaBibli — auteurs</PageTitle>
<h1 class="titre-page">Auteurs</h1>
@if (_erreur is not null)
{
<p class="message-erreur" role="alert">@_erreur</p>
}
@if (_suggestions is { Count: > 0 })
{
@*
Ce qui arrive ici est ce que la machine REFUSE de trancher. Les variantes sûres — casse,
accents, ordre « Nom, Prénom », initiales abrégées — ont déjà été réunies à l'écriture et
n'apparaissent jamais dans cette liste. Restent les cas ambigus, typiquement un patronyme
seul face à un nom complet : deux personnes d'une même famille écrivent aussi des livres.
*@
<section class="rapprochements">
<h2 class="titre-section">Rapprochements possibles</h2>
<p class="message-discret">
Ces noms se ressemblent sans qu'on puisse l'affirmer. Un patronyme seul peut désigner
une autre personne de la même famille — à vous de trancher. Une fusion se défait mal.
</p>
<ul class="liste-rapprochements">
@foreach (var suggestion in _suggestions)
{
<li class="carte-rapprochement" @key="(suggestion.Source.Id, suggestion.Cible.Id)">
<p class="rapprochement-noms">
<strong>@suggestion.Source.Nom</strong>
(@Livres(suggestion.Source.NombreDeLivres))
est-il <strong>@suggestion.Cible.Nom</strong>
(@Livres(suggestion.Cible.NombreDeLivres)) ?
</p>
<div class="actions-formulaire">
<button type="button" class="bouton bouton-principal" disabled="@_enCours"
@onclick="() => AccepterAsync(suggestion)">
Oui, réunir sous « @suggestion.Cible.Nom »
</button>
<button type="button" class="bouton bouton-discret" disabled="@_enCours"
@onclick="() => RefuserAsync(suggestion)">
Non, deux personnes
</button>
</div>
</li>
}
</ul>
</section>
}
@if (_auteurs is null)
{
<p class="message-discret">Chargement…</p>
}
else if (_auteurs.Count == 0)
{
<p class="message-discret">Aucun auteur : la bibliothèque est vide.</p>
}
else
{
<p class="compteur">@_auteurs.Count auteur@(_auteurs.Count > 1 ? "s" : "")</p>
<ul class="liste-auteurs">
@foreach (var auteur in _auteurs)
{
<li class="carte-auteur-ligne" @key="auteur.Id">
<a class="lien-auteur" href="@($"?auteur={auteur.Id}")">@auteur.Nom</a>
<span class="etiquette">@Livres(auteur.NombreDeLivres)</span>
@* Le declencheur decrit dans IDEES.md : depuis un auteur qu'on a deja, voir
tout ce qu'il a ecrit et marquer ce qui manque. *@
<a class="bouton bouton-compact bouton-discret"
href="@($"auteurs/{auteur.Id}/bibliographie")">Bibliographie</a>
</li>
}
</ul>
}
<nav class="actions-flottantes">
<a class="bouton bouton-discret" href="">Retour au catalogue</a>
<a class="bouton bouton-discret" href="souhaits">Ma liste d'envies</a>
</nav>
@code {
private IReadOnlyList<AuteurDto>? _auteurs;
private IReadOnlyList<SuggestionRapprochement>? _suggestions;
private bool _enCours;
private string? _erreur;
protected override Task OnInitializedAsync() => ChargerAsync();
private static string Livres(int nombre) => $"{nombre} livre{(nombre > 1 ? "s" : "")}";
private async Task ChargerAsync()
{
_erreur = null;
try
{
_auteurs = await Api.ListerAuteursAsync();
_suggestions = await Api.ListerRapprochementsAsync();
}
catch (Exception ex)
{
_erreur = $"Impossible de charger les auteurs : {ex.Message}";
}
}
private Task AccepterAsync(SuggestionRapprochement suggestion) =>
DeciderAsync(() => Api.AccepterRapprochementAsync(suggestion.Source.Id, suggestion.Cible.Id));
private Task RefuserAsync(SuggestionRapprochement suggestion) =>
DeciderAsync(() => Api.RefuserRapprochementAsync(suggestion.Source.Id, suggestion.Cible.Id));
private async Task DeciderAsync(Func<Task<string?>> action)
{
_enCours = true;
var erreur = await action();
_enCours = false;
if (erreur is not null)
{
_erreur = erreur;
return;
}
// Une fusion peut en rendre d'autres caduques (l'un des deux noms disparaît) : on relit
// la liste complète plutôt que de retirer la ligne traitée à la main.
await ChargerAsync();
}
}