Distinguer romans et BD par un type de document
Format disait physique ou numérique, jamais roman ou BD. Trois valeurs seulement : une valeur s'ajoute sans migration, la renommer une fois posée sur des centaines de fiches, non. Le défaut est « non précisé », pas « roman » : les fiches existantes n'ont pas été saisies avec cette question en tête, et un AddColumn suffit justement parce que la valeur par défaut ne prétend rien. L'interface n'affiche donc que ce qui a été choisi, comme pour le format. Filtre ajouté aux DEUX implémentations, serveur et hors-ligne, avec quatre jeux de critères de plus dans le test qui les confronte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,27 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@*
|
||||
⚠️ Le défaut est « non précisé », PAS « roman » : on ne remplit pas une case à la place
|
||||
de l'utilisateur. C'est aussi ce qui rend l'étiquette honnête dans les listes — elle ne
|
||||
s'affiche que lorsqu'un type a été choisi.
|
||||
*@
|
||||
<fieldset class="champ">
|
||||
<legend class="champ-libelle">
|
||||
Type<span class="champ-aide"> (facultatif)</span>
|
||||
</legend>
|
||||
<div class="segments">
|
||||
@foreach (var type in Types)
|
||||
{
|
||||
<button type="button"
|
||||
class="segment @(Saisie.TypeDocument == type ? "segment-actif" : null)"
|
||||
@onclick="() => Saisie.TypeDocument = type">
|
||||
@Libelles.TypeDocument(type)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="champ">
|
||||
<legend class="champ-libelle">
|
||||
Statut de lecture<span class="champ-aide"> (le vôtre)</span>
|
||||
@@ -112,6 +133,8 @@
|
||||
@code {
|
||||
private static readonly Format[] Formats = Enum.GetValues<Format>();
|
||||
|
||||
private static readonly TypeDocument[] Types = Enum.GetValues<TypeDocument>();
|
||||
|
||||
[Parameter, EditorRequired] public EnregistrementLivre Saisie { get; set; } = new();
|
||||
|
||||
[Parameter] public string LibelleValidation { get; set; } = "Enregistrer";
|
||||
|
||||
@@ -12,6 +12,19 @@ public static class Libelles
|
||||
_ => format.ToString(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Libellé du type de document. <b>« Non précisé » ne s'affiche pas sur une fiche</b> :
|
||||
/// c'est l'absence de choix, et l'interface ne montre que ce qui a été choisi. Le libellé
|
||||
/// existe pour les boutons de saisie et de filtre, où il faut bien nommer l'option.
|
||||
/// </summary>
|
||||
public static string TypeDocument(TypeDocument type) => type switch
|
||||
{
|
||||
MaBibli.Shared.Entites.TypeDocument.NonPrecise => "Non précisé",
|
||||
MaBibli.Shared.Entites.TypeDocument.Roman => "Roman",
|
||||
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "BD",
|
||||
_ => type.ToString(),
|
||||
};
|
||||
|
||||
public static string Statut(Statut statut) => statut switch
|
||||
{
|
||||
MaBibli.Shared.Entites.Statut.ALire => "À lire",
|
||||
|
||||
@@ -54,6 +54,23 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Même logique que pour le format : tant qu'aucun type n'a été renseigné, la ligne
|
||||
n'aurait qu'un seul choix utile. Elle apparaît dès la première fiche typée.
|
||||
⚠️ « Non précisé » y figure comme valeur à part entière : « qu'est-ce que je n'ai
|
||||
pas encore rangé ? » est justement la question qui se pose après coup. *@
|
||||
@if (PlusieursTypes)
|
||||
{
|
||||
<div class="segments" role="group" aria-label="Filtrer par type de document">
|
||||
<button type="button" class="segment @(_type is null ? "segment-actif" : null)"
|
||||
@onclick="() => FiltrerType(null)">Tous</button>
|
||||
@foreach (var type in Enum.GetValues<TypeDocument>())
|
||||
{
|
||||
<button type="button" class="segment @(_type == type ? "segment-actif" : null)"
|
||||
@onclick="() => FiltrerType(type)">@Libelles.TypeDocument(type)</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@* ⚠️ Ce filtre porte sur MON statut, pas sur celui du foyer : un livre sans statut
|
||||
pour moi ne remonte sous aucune valeur, il reste visible sous « Tous ». L'écran
|
||||
l'assume au lieu de laisser croire à une vue commune. *@
|
||||
@@ -125,6 +142,13 @@ else if (_livres is not null)
|
||||
<span class="etiquette">@Libelles.Format(livre.Format)</span>
|
||||
}
|
||||
|
||||
@* Même règle que le format : on n'affiche que ce qui a été CHOISI.
|
||||
« Non précisé » n'apprendrait rien et serait sur presque chaque ligne. *@
|
||||
@if (livre.TypeDocument != TypeDocument.NonPrecise)
|
||||
{
|
||||
<span class="etiquette">@Libelles.TypeDocument(livre.TypeDocument)</span>
|
||||
}
|
||||
|
||||
@* Personnel : ce que montre cette pastille est MA lecture, pas celle du foyer.
|
||||
Pas de pastille = non commencé, l'absence de ligne en base. *@
|
||||
@if (livre.Statut is { } statut)
|
||||
@@ -184,6 +208,7 @@ else if (_livres is not null)
|
||||
private int? _auteurCharge;
|
||||
private string _recherche = string.Empty;
|
||||
private Format? _format;
|
||||
private TypeDocument? _type;
|
||||
private Statut? _statut;
|
||||
private bool _chargement;
|
||||
private bool _filtresOuverts;
|
||||
@@ -203,6 +228,11 @@ else if (_livres is not null)
|
||||
|
||||
private bool PlusieursFormats => _formatsDuFonds is null || _formatsDuFonds.Count > 1;
|
||||
|
||||
/// <summary>Types réellement présents dans le fonds. Mêmes précautions que les formats.</summary>
|
||||
private IReadOnlySet<TypeDocument>? _typesDuFonds;
|
||||
|
||||
private bool PlusieursTypes => _typesDuFonds is null || _typesDuFonds.Count > 1;
|
||||
|
||||
/// <summary>
|
||||
/// Nombre de filtres repliés actuellement actifs, affiché sur le bouton « Filtrer ».
|
||||
/// </summary>
|
||||
@@ -211,14 +241,15 @@ else if (_livres is not null)
|
||||
/// déjà à l'écran, l'une dans son champ, l'autre dans sa phrase. Ce compteur ne dit que ce
|
||||
/// qui serait invisible une fois le panneau refermé.
|
||||
/// </remarks>
|
||||
private int FiltresActifs => (_format is null ? 0 : 1) + (_statut is null ? 0 : 1);
|
||||
private int FiltresActifs =>
|
||||
(_format is null ? 0 : 1) + (_type is null ? 0 : 1) + (_statut is null ? 0 : 1);
|
||||
|
||||
// Une frappe au clavier annule la recherche précédente encore en vol.
|
||||
private CancellationTokenSource _cts = new();
|
||||
|
||||
private bool EstFiltre =>
|
||||
!string.IsNullOrWhiteSpace(_recherche) || _format is not null || _statut is not null
|
||||
|| AuteurId is not null;
|
||||
!string.IsNullOrWhiteSpace(_recherche) || _format is not null || _type is not null
|
||||
|| _statut is not null || AuteurId is not null;
|
||||
|
||||
/// <summary>
|
||||
/// La liste se relit à chaque bascule du réseau : au retour, pour reprendre les données du
|
||||
@@ -255,6 +286,12 @@ else if (_livres is not null)
|
||||
return ChargerAsync();
|
||||
}
|
||||
|
||||
private Task FiltrerType(TypeDocument? type)
|
||||
{
|
||||
_type = type;
|
||||
return ChargerAsync();
|
||||
}
|
||||
|
||||
private Task FiltrerStatut(Statut? statut)
|
||||
{
|
||||
_statut = statut;
|
||||
@@ -265,6 +302,7 @@ else if (_livres is not null)
|
||||
private static bool EstSansCritere(CritereLivres criteres) =>
|
||||
string.IsNullOrWhiteSpace(criteres.Recherche)
|
||||
&& criteres.Format is null
|
||||
&& criteres.TypeDocument is null
|
||||
&& criteres.Statut is null
|
||||
&& criteres.AuteurId is null;
|
||||
|
||||
@@ -284,6 +322,7 @@ else if (_livres is not null)
|
||||
{
|
||||
Recherche = _recherche,
|
||||
Format = _format,
|
||||
TypeDocument = _type,
|
||||
Statut = _statut,
|
||||
AuteurId = AuteurId,
|
||||
};
|
||||
@@ -293,6 +332,7 @@ else if (_livres is not null)
|
||||
if (EstSansCritere(criteres))
|
||||
{
|
||||
_formatsDuFonds = _livres.Select(l => l.Format).ToHashSet();
|
||||
_typesDuFonds = _livres.Select(l => l.TypeDocument).ToHashSet();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -127,6 +127,12 @@ else
|
||||
{
|
||||
<div><dt>Format</dt><dd>@Libelles.Format(_livre.Format)</dd></div>
|
||||
}
|
||||
|
||||
@* Idem : « non précisé » est l'absence de réponse, pas une réponse. *@
|
||||
@if (_livre.TypeDocument != TypeDocument.NonPrecise)
|
||||
{
|
||||
<div><dt>Type</dt><dd>@Libelles.TypeDocument(_livre.TypeDocument)</dd></div>
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -274,6 +280,7 @@ else
|
||||
Auteurs = livre.Auteurs.Select(a => a.Nom).ToList(),
|
||||
Editeur = livre.Editeur,
|
||||
Format = livre.Format,
|
||||
TypeDocument = livre.TypeDocument,
|
||||
Statut = livre.Statut,
|
||||
CoverUrl = livre.CoverUrl,
|
||||
};
|
||||
|
||||
@@ -36,6 +36,11 @@ public static class FiltreLivresLocal
|
||||
livres = livres.Where(l => l.Format == format);
|
||||
}
|
||||
|
||||
if (criteres.TypeDocument is { } type)
|
||||
{
|
||||
livres = livres.Where(l => l.TypeDocument == type);
|
||||
}
|
||||
|
||||
if (criteres.Statut is { } statut)
|
||||
{
|
||||
// LivreDto.Statut est déjà celui de l'utilisateur courant : le serveur l'a résolu
|
||||
|
||||
@@ -76,6 +76,11 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat
|
||||
parametres.Add($"format={format}");
|
||||
}
|
||||
|
||||
if (criteres.TypeDocument is { } type)
|
||||
{
|
||||
parametres.Add($"typeDocument={type}");
|
||||
}
|
||||
|
||||
if (criteres.Statut is { } statut)
|
||||
{
|
||||
parametres.Add($"statut={statut}");
|
||||
|
||||
Reference in New Issue
Block a user