Clôt la décision du premier lot, qui écartait les périodiques faute de modèle. Douze numéros d'un magazine ne font plus douze fiches identiques. Table à part, pour la troisième fois et pour le même motif que la liste d'envies et les séries : logée dans Livres, une revue entrerait dans le catalogue, ses compteurs, les doublons et les bibliographies, et il faudrait écrire « et qui n'est pas une revue » à chaque lecture. Un numéro est recensé, rien de plus — ni prêt ni statut, tous deux liés à Livre par clé étrangère. Réversible. « Créer ou retrouver » et non « créer » : scanner le numéro suivant du même magazine retombe forcément sur la même revue. L'unicité de l'ISSN est un index partiel, sans quoi les revues sans ISSN se bloqueraient entre elles. Le flux 977 mène désormais à la fiche de la revue, et ne pré-remplit plus le formulaire d'un livre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
135 lines
4.0 KiB
Plaintext
135 lines
4.0 KiB
Plaintext
@page "/revues"
|
|
@inject ServiceLivresApi Api
|
|
@inject EtatReseau Reseau
|
|
@implements IDisposable
|
|
|
|
<PageTitle>MaBibli — revues</PageTitle>
|
|
|
|
<h1 class="titre-page">Revues</h1>
|
|
|
|
<p class="message-discret">
|
|
Une fiche par revue, les numéros à l'intérieur — douze numéros d'un magazine ne font pas
|
|
douze fiches. Les revues ne sont pas au catalogue : ce ne sont pas des livres.
|
|
</p>
|
|
|
|
@if (_erreur is not null)
|
|
{
|
|
<p class="message-erreur" role="alert">@_erreur</p>
|
|
}
|
|
|
|
<div class="barre-recherche">
|
|
<input class="champ-saisie" type="text" placeholder="Nom d'une revue…"
|
|
@bind="_nouvelle" @bind:event="oninput" @onkeydown="SurTouche"
|
|
aria-label="Nom de la nouvelle revue" autocomplete="off" />
|
|
</div>
|
|
|
|
<div class="actions-formulaire">
|
|
<button type="button" class="bouton bouton-principal"
|
|
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_nouvelle))"
|
|
title="@MotifBlocage"
|
|
@onclick="CreerAsync">
|
|
Ajouter la revue
|
|
</button>
|
|
</div>
|
|
|
|
@if (_revues is null)
|
|
{
|
|
<p class="message-discret" role="status">Chargement…</p>
|
|
}
|
|
else if (_revues.Count == 0)
|
|
{
|
|
<p class="message-discret">
|
|
Aucune revue. Scannez le code-barres d'un magazine, ou saisissez son nom ci-dessus :
|
|
le code d'une revue commence par 977 et porte un ISSN, pas un ISBN.
|
|
</p>
|
|
}
|
|
else
|
|
{
|
|
<ul class="liste-series">
|
|
@foreach (var revue in _revues)
|
|
{
|
|
<li class="carte-serie" @key="revue.Id">
|
|
<a class="carte-titre" href="@($"revues/{revue.Id}")">@revue.Titre</a>
|
|
|
|
<p class="carte-details">
|
|
<span>
|
|
@(revue.Numeros.Count == 0
|
|
? "Aucun numéro"
|
|
: $"{revue.Numeros.Count} numéro{(revue.Numeros.Count > 1 ? "s" : "")}")
|
|
</span>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(revue.Issn))
|
|
{
|
|
<span class="code-isbn">ISSN @revue.Issn</span>
|
|
}
|
|
|
|
@if (!string.IsNullOrWhiteSpace(revue.Editeur))
|
|
{
|
|
<span>@revue.Editeur</span>
|
|
}
|
|
</p>
|
|
|
|
@* Le dernier numéro reçu répond seul à « est-ce que je l'ai déjà ? », qui est la
|
|
question qu'on se pose devant un kiosque. *@
|
|
@if (revue.Numeros.FirstOrDefault() is { } dernier)
|
|
{
|
|
<p class="carte-details">Dernier : n° @dernier.Numero @Quand(dernier)</p>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
|
|
@code {
|
|
private IReadOnlyList<RevueDto>? _revues;
|
|
private string _nouvelle = string.Empty;
|
|
private bool _enCours;
|
|
private string? _erreur;
|
|
|
|
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
|
|
|
private static string Quand(NumeroRevueDto numero) =>
|
|
numero.DateParution is { } date ? $"({date.ToLocalTime():MM/yyyy})" : string.Empty;
|
|
|
|
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() => _revues = await Api.ListerRevuesAsync();
|
|
|
|
private async Task SurTouche(KeyboardEventArgs e)
|
|
{
|
|
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_nouvelle) && Reseau.EnLigne)
|
|
{
|
|
await CreerAsync();
|
|
}
|
|
}
|
|
|
|
private async Task CreerAsync()
|
|
{
|
|
_enCours = true;
|
|
_erreur = null;
|
|
|
|
var resultat = await Api.CreerRevueAsync(new EnregistrementRevue { Titre = _nouvelle });
|
|
|
|
_enCours = false;
|
|
|
|
if (!resultat.EstOk)
|
|
{
|
|
_erreur = resultat.Erreur;
|
|
return;
|
|
}
|
|
|
|
_nouvelle = string.Empty;
|
|
await ChargerAsync();
|
|
}
|
|
}
|