Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2254e700d |
@@ -43,7 +43,8 @@
|
|||||||
<ul class="liste-livres">
|
<ul class="liste-livres">
|
||||||
@foreach (var sous in Entree.SousEntrees)
|
@foreach (var sous in Entree.SousEntrees)
|
||||||
{
|
{
|
||||||
<GroupeCatalogue Entree="sous" Ouverts="Ouverts" Totaux="Totaux" />
|
<GroupeCatalogue Entree="sous" Ouverts="Ouverts" Totaux="Totaux"
|
||||||
|
LotActif="LotActif" SelectionLot="SelectionLot" OnSelectionner="OnSelectionner" />
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -52,11 +53,26 @@
|
|||||||
{
|
{
|
||||||
<ul class="liste-livres">
|
<ul class="liste-livres">
|
||||||
@foreach (var livre in Entree.Livres)
|
@foreach (var livre in Entree.Livres)
|
||||||
|
{
|
||||||
|
@if (LotActif)
|
||||||
|
{
|
||||||
|
<li class="carte-oeuvre" @key="livre.Id">
|
||||||
|
<label class="oeuvre-case">
|
||||||
|
<input type="checkbox"
|
||||||
|
checked="@(SelectionLot?.Contains(livre.Id) ?? false)"
|
||||||
|
@onchange="e => OnSelectionner.InvokeAsync((livre.Id, e.Value is true))" />
|
||||||
|
<span class="lecture-ecran">Sélectionner « @livre.Titre »</span>
|
||||||
|
</label>
|
||||||
|
<CarteLivre Livre="livre" />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<li @key="livre.Id">
|
<li @key="livre.Id">
|
||||||
<CarteLivre Livre="livre" />
|
<CarteLivre Livre="livre" />
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,6 +101,19 @@
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Parameter] public IReadOnlyDictionary<int, (int Possedes, int Total)>? Totaux { get; set; }
|
[Parameter] public IReadOnlyDictionary<int, (int Possedes, int Total)>? Totaux { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Modification en lot du catalogue : voir la remarque équivalente sur Catalogue.razor.</summary>
|
||||||
|
[Parameter] public bool LotActif { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public ISet<int>? SelectionLot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⚠️ EventCallback, et non un Action brut : coché depuis un tome niché dans ce bloc, l'écran
|
||||||
|
/// du catalogue (le VRAI propriétaire de la sélection) ne serait sinon jamais prévenu du
|
||||||
|
/// changement, et sa barre de sélection resterait figée sur l'état d'avant — le second piège
|
||||||
|
/// Blazor de CLAUDE.md.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public EventCallback<(int LivreId, bool Coche)> OnSelectionner { get; set; }
|
||||||
|
|
||||||
private (int Possedes, int Total) Compte =>
|
private (int Possedes, int Total) Compte =>
|
||||||
Totaux is not null && Totaux.TryGetValue(Entree.Serie!.Id, out var connu)
|
Totaux is not null && Totaux.TryGetValue(Entree.Serie!.Id, out var connu)
|
||||||
? connu
|
? connu
|
||||||
|
|||||||
@@ -40,6 +40,20 @@
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@*
|
||||||
|
« Modifier en lot » n'apparaît qu'à partir de DEUX livres — même seuil que sur la fiche
|
||||||
|
série. Reste possible hors-ligne : l'écriture, elle, est bloquée par livre comme
|
||||||
|
partout ailleurs (⚠️ MotifBlocage).
|
||||||
|
*@
|
||||||
|
@if (Reseau.EnLigne && _livres is { Count: > 1 })
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret bouton-compact"
|
||||||
|
aria-expanded="@Aria.Etat(_lotOuvert)" aria-controls="lot-catalogue"
|
||||||
|
@onclick="BasculerLot">
|
||||||
|
Modifier en lot
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
@if (Reseau.EnLigne)
|
@if (Reseau.EnLigne)
|
||||||
{
|
{
|
||||||
<a class="bouton bouton-principal bouton-compact" href="ajout">Ajouter un ouvrage</a>
|
<a class="bouton bouton-principal bouton-compact" href="ajout">Ajouter un ouvrage</a>
|
||||||
@@ -226,6 +240,47 @@ else if (_livres is not null)
|
|||||||
{
|
{
|
||||||
<p class="compteur">@_livres.Count livre@(_livres.Count > 1 ? "s" : "")</p>
|
<p class="compteur">@_livres.Count livre@(_livres.Count > 1 ? "s" : "")</p>
|
||||||
|
|
||||||
|
@*
|
||||||
|
Modification en lot : même patron que la fiche série (barre collante, formulaire affiché
|
||||||
|
sous elle) — sauf que la sélection porte ici sur le catalogue ENTIER, groupé ou non, d'où
|
||||||
|
le bouton « Tout sélectionner » qui coche d'un coup tous les livres actuellement chargés
|
||||||
|
(donc déjà filtrés : cocher les 400 livres du fonds derrière un filtre à trois résultats
|
||||||
|
n'aurait aucun sens).
|
||||||
|
*@
|
||||||
|
@if (_lotOuvert)
|
||||||
|
{
|
||||||
|
<div id="lot-catalogue">
|
||||||
|
@if (_selectionLot.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="barre-selection" role="group" aria-label="Actions sur la sélection">
|
||||||
|
<span class="selection-compte">
|
||||||
|
@_selectionLot.Count sélectionné@(_selectionLot.Count > 1 ? "s" : "")
|
||||||
|
</span>
|
||||||
|
<button type="button" class="bouton bouton-compact bouton-discret"
|
||||||
|
disabled="@_enCoursLot" @onclick="ToutSelectionnerLot">
|
||||||
|
Tout sélectionner (@_livres!.Count)
|
||||||
|
</button>
|
||||||
|
<button type="button" class="bouton bouton-compact bouton-discret"
|
||||||
|
disabled="@_enCoursLot" @onclick="() => _selectionLot.Clear()">
|
||||||
|
Tout décocher
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormulaireLotLivres Saisie="_saisieLot" NombreLivres="_selectionLot.Count"
|
||||||
|
Erreur="@_erreurLot" EnCours="_enCoursLot" MessageBlocage="@MotifBlocageLot"
|
||||||
|
OnValider="EnregistrerLotAsync" OnAnnuler="AnnulerLot" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="message-discret">
|
||||||
|
Cochez les livres à modifier ensemble, ou
|
||||||
|
<button type="button" class="bouton bouton-discret bouton-compact"
|
||||||
|
@onclick="ToutSelectionnerLot">tout sélectionner (@_livres!.Count)</button>.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@*
|
@*
|
||||||
Les tomes d'une série tiennent sous son nom, à la place qu'occuperait le premier d'entre
|
Les tomes d'une série tiennent sous son nom, à la place qu'occuperait le premier d'entre
|
||||||
eux : l'ordre général du catalogue ne bouge pas, on ne perd donc pas un livre qu'on
|
eux : l'ordre général du catalogue ne bouge pas, on ne perd donc pas un livre qu'on
|
||||||
@@ -247,7 +302,21 @@ else if (_livres is not null)
|
|||||||
{
|
{
|
||||||
@if (entree.EstGroupe)
|
@if (entree.EstGroupe)
|
||||||
{
|
{
|
||||||
<GroupeCatalogue Entree="entree" Ouverts="_ouverts" Totaux="_totauxSeries" />
|
<GroupeCatalogue Entree="entree" Ouverts="_ouverts" Totaux="_totauxSeries"
|
||||||
|
LotActif="_lotOuvert" SelectionLot="_selectionLot"
|
||||||
|
OnSelectionner="args => SelectionnerLot(args.LivreId, args.Coche)" />
|
||||||
|
}
|
||||||
|
else if (_lotOuvert)
|
||||||
|
{
|
||||||
|
<li class="carte-oeuvre" @key="entree.Livres[0].Id">
|
||||||
|
<label class="oeuvre-case">
|
||||||
|
<input type="checkbox"
|
||||||
|
checked="@_selectionLot.Contains(entree.Livres[0].Id)"
|
||||||
|
@onchange="e => SelectionnerLot(entree.Livres[0].Id, e.Value is true)" />
|
||||||
|
<span class="lecture-ecran">Sélectionner « @entree.Livres[0].Titre »</span>
|
||||||
|
</label>
|
||||||
|
<CarteLivre Livre="entree.Livres[0]" />
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -348,6 +417,18 @@ else if (_livres is not null)
|
|||||||
private bool _filtresOuverts;
|
private bool _filtresOuverts;
|
||||||
private string? _erreur;
|
private string? _erreur;
|
||||||
|
|
||||||
|
/// <summary>Le panneau de modification en lot est déplié. Même patron que la fiche série.</summary>
|
||||||
|
private bool _lotOuvert;
|
||||||
|
|
||||||
|
/// <summary>Identifiants des livres cochés pour la modification en lot.</summary>
|
||||||
|
private readonly HashSet<int> _selectionLot = [];
|
||||||
|
|
||||||
|
private SaisieLot _saisieLot = new();
|
||||||
|
private bool _enCoursLot;
|
||||||
|
private string? _erreurLot;
|
||||||
|
|
||||||
|
private string? MotifBlocageLot => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formats réellement présents dans le fonds, ou <c>null</c> tant qu'on ne le sait pas.
|
/// Formats réellement présents dans le fonds, ou <c>null</c> tant qu'on ne le sait pas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -606,6 +687,110 @@ else if (_livres is not null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ouvre ou ferme le panneau de modification en lot. Le refermer vide aussi la sélection et
|
||||||
|
/// la saisie en cours — même règle que sur la fiche série : un panneau qui se rouvrirait sur
|
||||||
|
/// l'état d'avant laisserait croire à une saisie encore active.
|
||||||
|
/// </summary>
|
||||||
|
private void BasculerLot()
|
||||||
|
{
|
||||||
|
_lotOuvert = !_lotOuvert;
|
||||||
|
_erreurLot = null;
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectionnerLot(int livreId, bool selectionne)
|
||||||
|
{
|
||||||
|
if (selectionne)
|
||||||
|
{
|
||||||
|
_selectionLot.Add(livreId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectionLot.Remove(livreId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coche d'un coup tous les livres actuellement CHARGÉS — donc déjà filtrés. Un groupe replié
|
||||||
|
/// n'y échappe pas : la sélection porte sur <c>_livres</c>, pas sur ce que l'écran affiche.
|
||||||
|
/// </summary>
|
||||||
|
private void ToutSelectionnerLot()
|
||||||
|
{
|
||||||
|
foreach (var livre in _livres ?? [])
|
||||||
|
{
|
||||||
|
_selectionLot.Add(livre.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnnulerLot()
|
||||||
|
{
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
_erreurLot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applique la saisie en lot à chacun des livres cochés, un appel à la fois — même patron que
|
||||||
|
/// la fiche série : pas de point d'entrée dédié, la boucle réutilise
|
||||||
|
/// <see cref="ServiceLivresApi.ModifierAsync"/>, en ne remplaçant que les champs cochés et en
|
||||||
|
/// reprenant tels quels ceux qui ne le sont pas.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnregistrerLotAsync()
|
||||||
|
{
|
||||||
|
_enCoursLot = true;
|
||||||
|
_erreurLot = null;
|
||||||
|
|
||||||
|
var erreurs = new List<string>();
|
||||||
|
|
||||||
|
foreach (var livreId in _selectionLot)
|
||||||
|
{
|
||||||
|
var livre = (_livres ?? []).FirstOrDefault(l => l.Id == livreId);
|
||||||
|
if (livre is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var saisie = new EnregistrementLivre
|
||||||
|
{
|
||||||
|
Isbn = livre.Isbn,
|
||||||
|
Titre = livre.Titre,
|
||||||
|
Auteurs = _saisieLot.ModifierAuteurs
|
||||||
|
? _saisieLot.Auteurs
|
||||||
|
: livre.Auteurs.Select(a => new AuteurSaisi(a.Nom, a.Role)).ToList(),
|
||||||
|
Themes = _saisieLot.ModifierThemes ? _saisieLot.Themes : [.. livre.Themes],
|
||||||
|
Editeur = _saisieLot.ModifierEditeur ? _saisieLot.Editeur : livre.Editeur,
|
||||||
|
Format = _saisieLot.ModifierFormat ? _saisieLot.Format : livre.Format,
|
||||||
|
TypeDocument = _saisieLot.ModifierType ? _saisieLot.TypeDocument : livre.TypeDocument,
|
||||||
|
Statut = _saisieLot.ModifierStatut ? _saisieLot.Statut : livre.Statut,
|
||||||
|
NombrePages = livre.NombrePages,
|
||||||
|
CoverUrl = livre.CoverUrl,
|
||||||
|
UrlNotice = livre.UrlNotice,
|
||||||
|
};
|
||||||
|
|
||||||
|
var resultat = await Api.ModifierAsync(livre.Id, saisie);
|
||||||
|
if (!resultat.EstOk)
|
||||||
|
{
|
||||||
|
erreurs.Add(resultat.Erreur!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_enCoursLot = false;
|
||||||
|
|
||||||
|
if (erreurs.Count > 0)
|
||||||
|
{
|
||||||
|
_erreurLot = string.Join(" ", erreurs.Distinct());
|
||||||
|
await ChargerAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lotOuvert = false;
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
await ChargerAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Reseau.Change -= SurChangementReseau;
|
Reseau.Change -= SurChangementReseau;
|
||||||
|
|||||||
Reference in New Issue
Block a user