Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d07795611d | ||
|
|
0f2b81346a | ||
|
|
e7fe9ed6a9 | ||
|
|
dbb103f6db | ||
|
|
e2254e700d | ||
|
|
47d371fa3a | ||
|
|
61d4fb1678 |
@@ -0,0 +1,699 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using MaBibli.Api.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MaBibli.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MaBibliDbContext))]
|
||||||
|
[Migration("20260910204753_AjouterEtatSerie")]
|
||||||
|
partial class AjouterEtatSerie
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumeroRevueId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NumeroRevueId", "TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ArticlesUne");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("CleRegroupement")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Nom")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NomNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CleRegroupement")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("NomNormalise");
|
||||||
|
|
||||||
|
b.ToTable("Auteurs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.BibliographieMasquee", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Utilisateur", "AuteurId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "AuteurId", "Utilisateur", "TitreNormalise" }, "IX_BibliographiesMasquees_Auteur_Utilisateur_Titre")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BibliographiesMasquees");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SerieId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LivreId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "SerieId" }, "IX_ElementsSerie_SerieId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "SerieId", "LivreId" }, "IX_ElementsSerie_SerieId_LivreId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"LivreId\" IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("ElementsSerie");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Isbn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("NombrePages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TypeDocument")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UrlNotice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Isbn");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise");
|
||||||
|
|
||||||
|
b.ToTable("Livres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("LivreId", "AuteurId");
|
||||||
|
|
||||||
|
b.HasIndex("AuteurId");
|
||||||
|
|
||||||
|
b.ToTable("LivreAuteurs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreSouhaite", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Annee")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Auteur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AuteurNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Isbn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Rang")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur" }, "IX_LivresSouhaites_Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "AuteurNormalise" }, "IX_LivresSouhaites_Utilisateur_Oeuvre")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LivresSouhaites");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("LivreId", "ThemeId");
|
||||||
|
|
||||||
|
b.HasIndex("ThemeId");
|
||||||
|
|
||||||
|
b.ToTable("LivreThemes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateParution")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NumeroNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("RevueId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RevueId", "NumeroNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("NumerosRevue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DatePret")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateRetour")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Emprunteur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId_EnCours")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"DateRetour\" IS NULL");
|
||||||
|
|
||||||
|
b.ToTable("Prets");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.RapprochementRefuse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurAId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurBId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuteurAId", "AuteurBId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RapprochementsRefuses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Issn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Issn" }, "IX_Revues_Issn")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"Issn\" IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("Revues");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.RevueSouhaitee", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Issn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NumeroNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<int>("Rang")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur" }, "IX_RevuesSouhaitees_Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "NumeroNormalise" }, "IX_RevuesSouhaitees_Utilisateur_Revue")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RevuesSouhaitees");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Etat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("SerieParenteId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SerieParenteId");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateMaj")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Statut")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex("LivreId", "Utilisateur")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("StatutsLecture");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Nom")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NomNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NomNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Themes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.NumeroRevue", "NumeroRevue")
|
||||||
|
.WithMany("Articles")
|
||||||
|
.HasForeignKey("NumeroRevueId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("NumeroRevue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Serie", "Serie")
|
||||||
|
.WithMany("Elements")
|
||||||
|
.HasForeignKey("SerieId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
|
||||||
|
b.Navigation("Serie");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Auteur", "Auteur")
|
||||||
|
.WithMany("Livres")
|
||||||
|
.HasForeignKey("AuteurId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Auteurs")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Auteur");
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Themes")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Theme", "Theme")
|
||||||
|
.WithMany("Livres")
|
||||||
|
.HasForeignKey("ThemeId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
|
||||||
|
b.Navigation("Theme");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Revue", "Revue")
|
||||||
|
.WithMany("Numeros")
|
||||||
|
.HasForeignKey("RevueId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Revue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Prets")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Serie", "SerieParente")
|
||||||
|
.WithMany("SousSeries")
|
||||||
|
.HasForeignKey("SerieParenteId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("SerieParente");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Statuts")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Livres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Auteurs");
|
||||||
|
|
||||||
|
b.Navigation("Prets");
|
||||||
|
|
||||||
|
b.Navigation("Statuts");
|
||||||
|
|
||||||
|
b.Navigation("Themes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Articles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Numeros");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Elements");
|
||||||
|
|
||||||
|
b.Navigation("SousSeries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Livres");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MaBibli.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AjouterEtatSerie : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Etat",
|
||||||
|
table: "Series",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Etat",
|
||||||
|
table: "Series");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+699
@@ -0,0 +1,699 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using MaBibli.Api.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MaBibli.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MaBibliDbContext))]
|
||||||
|
[Migration("20260910214613_AjouterTypesPratiquetConnaissance")]
|
||||||
|
partial class AjouterTypesPratiquetConnaissance
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumeroRevueId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NumeroRevueId", "TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ArticlesUne");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("CleRegroupement")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Nom")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NomNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CleRegroupement")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("NomNormalise");
|
||||||
|
|
||||||
|
b.ToTable("Auteurs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.BibliographieMasquee", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Utilisateur", "AuteurId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "AuteurId", "Utilisateur", "TitreNormalise" }, "IX_BibliographiesMasquees_Auteur_Utilisateur_Titre")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BibliographiesMasquees");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SerieId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LivreId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "SerieId" }, "IX_ElementsSerie_SerieId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "SerieId", "LivreId" }, "IX_ElementsSerie_SerieId_LivreId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"LivreId\" IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("ElementsSerie");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Isbn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("NombrePages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TypeDocument")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UrlNotice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Isbn");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise");
|
||||||
|
|
||||||
|
b.ToTable("Livres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("LivreId", "AuteurId");
|
||||||
|
|
||||||
|
b.HasIndex("AuteurId");
|
||||||
|
|
||||||
|
b.ToTable("LivreAuteurs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreSouhaite", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Annee")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Auteur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AuteurNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Isbn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Rang")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur" }, "IX_LivresSouhaites_Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "AuteurNormalise" }, "IX_LivresSouhaites_Utilisateur_Oeuvre")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LivresSouhaites");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("LivreId", "ThemeId");
|
||||||
|
|
||||||
|
b.HasIndex("ThemeId");
|
||||||
|
|
||||||
|
b.ToTable("LivreThemes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateParution")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NumeroNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("RevueId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RevueId", "NumeroNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("NumerosRevue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DatePret")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateRetour")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Emprunteur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId_EnCours")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"DateRetour\" IS NULL");
|
||||||
|
|
||||||
|
b.ToTable("Prets");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.RapprochementRefuse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurAId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AuteurBId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuteurAId", "AuteurBId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RapprochementsRefuses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Editeur")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Issn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Issn" }, "IX_Revues_Issn")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"Issn\" IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("Revues");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.RevueSouhaitee", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Issn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Numero")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NumeroNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<int>("Rang")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur" }, "IX_RevuesSouhaitees_Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "NumeroNormalise" }, "IX_RevuesSouhaitees_Utilisateur_Revue")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RevuesSouhaitees");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AjoutePar")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAjout")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Etat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("SerieParenteId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Titre")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TitreNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SerieParenteId");
|
||||||
|
|
||||||
|
b.HasIndex("TitreNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateMaj")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LivreId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Statut")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Utilisateur")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Utilisateur");
|
||||||
|
|
||||||
|
b.HasIndex("LivreId", "Utilisateur")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("StatutsLecture");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Nom")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NomNormalise")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NomNormalise")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Themes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.NumeroRevue", "NumeroRevue")
|
||||||
|
.WithMany("Articles")
|
||||||
|
.HasForeignKey("NumeroRevueId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("NumeroRevue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Serie", "Serie")
|
||||||
|
.WithMany("Elements")
|
||||||
|
.HasForeignKey("SerieId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
|
||||||
|
b.Navigation("Serie");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Auteur", "Auteur")
|
||||||
|
.WithMany("Livres")
|
||||||
|
.HasForeignKey("AuteurId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Auteurs")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Auteur");
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Themes")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Theme", "Theme")
|
||||||
|
.WithMany("Livres")
|
||||||
|
.HasForeignKey("ThemeId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
|
||||||
|
b.Navigation("Theme");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Revue", "Revue")
|
||||||
|
.WithMany("Numeros")
|
||||||
|
.HasForeignKey("RevueId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Revue");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Prets")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Serie", "SerieParente")
|
||||||
|
.WithMany("SousSeries")
|
||||||
|
.HasForeignKey("SerieParenteId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("SerieParente");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MaBibli.Shared.Entites.Livre", "Livre")
|
||||||
|
.WithMany("Statuts")
|
||||||
|
.HasForeignKey("LivreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Livre");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Livres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Auteurs");
|
||||||
|
|
||||||
|
b.Navigation("Prets");
|
||||||
|
|
||||||
|
b.Navigation("Statuts");
|
||||||
|
|
||||||
|
b.Navigation("Themes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Articles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Numeros");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Elements");
|
||||||
|
|
||||||
|
b.Navigation("SousSeries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Livres");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MaBibli.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AjouterTypesPratiquetConnaissance : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -465,6 +465,9 @@ namespace MaBibli.Api.Data.Migrations
|
|||||||
b.Property<DateTime>("DateAjout")
|
b.Property<DateTime>("DateAjout")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Etat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Position")
|
b.Property<int>("Position")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
@inject ServiceLivresApi Api
|
||||||
|
|
||||||
|
@*
|
||||||
|
Panneau de modification en lot, ouvert depuis la fiche série une fois des tomes cochés.
|
||||||
|
|
||||||
|
⚠️ Reprend le DESIGN de FormulaireLivre.razor (mêmes composants, même ordre de champs), mais
|
||||||
|
chaque champ est précédé d'une case « Modifier… » : sans elle, la valeur par défaut d'un champ
|
||||||
|
(« NonPrecise », « Physique »…) est déjà une vraie valeur, et l'appliquer à tous les livres
|
||||||
|
sélectionnés écraserait un champ qu'on ne voulait pas toucher (CLAUDE.md, invariant 10).
|
||||||
|
Un champ non coché ne s'affiche pas — c'est ce qui rend visible ce qui va être écrasé.
|
||||||
|
*@
|
||||||
|
<div class="formulaire">
|
||||||
|
<p class="message-avertissement" role="status">
|
||||||
|
Les champs cochés remplacent la valeur existante sur chacun des @NombreLivres livres
|
||||||
|
sélectionnés — pour les auteurs, une liste vide les retire tous.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<fieldset class="champ">
|
||||||
|
<legend class="champ-libelle">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierType" />
|
||||||
|
<span>Type</span>
|
||||||
|
</label>
|
||||||
|
</legend>
|
||||||
|
@if (Saisie.ModifierType)
|
||||||
|
{
|
||||||
|
<div class="segments">
|
||||||
|
@foreach (var type in Types)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="segment @Libelles.ClasseTypeDocument(type) @(Saisie.TypeDocument == type ? "segment-actif" : null)"
|
||||||
|
@onclick="() => Saisie.TypeDocument = type">
|
||||||
|
@Libelles.TypeDocument(type)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="champ">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierThemes" />
|
||||||
|
<span>Thèmes</span>
|
||||||
|
</label>
|
||||||
|
@if (Saisie.ModifierThemes)
|
||||||
|
{
|
||||||
|
<SaisieListe Valeurs="Saisie.Themes" Libelle="Thèmes" Aide=" (un par un : tapez, puis Entrée)"
|
||||||
|
Placeholder="dark fantasy" MessageDoublon="Ce thème est déjà dans la liste."
|
||||||
|
Suggestions="_themesConnus"
|
||||||
|
OnChangement="StateHasChanged" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="champ">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierAuteurs" />
|
||||||
|
<span>Auteurs</span>
|
||||||
|
</label>
|
||||||
|
@if (Saisie.ModifierAuteurs)
|
||||||
|
{
|
||||||
|
<SaisieListe Valeurs="Noms" Libelle="Auteurs" Aide=" (un par un : tapez, puis Entrée)"
|
||||||
|
Placeholder="Prénom Nom, ou Nom, Prénom"
|
||||||
|
MessageDoublon="Cet auteur est déjà dans la liste."
|
||||||
|
OnChangement="StateHasChanged">
|
||||||
|
<Complement Context="index">
|
||||||
|
@if (Saisie.Auteurs.Count > 1)
|
||||||
|
{
|
||||||
|
<div class="segments segments-role" role="group"
|
||||||
|
aria-label="@($"Rôle de {Saisie.Auteurs[index].Nom}")">
|
||||||
|
@foreach (var role in Roles)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="segment @(Saisie.Auteurs[index].Role == role ? "segment-actif" : null)"
|
||||||
|
@onclick="() => PoserRole(index, role)">
|
||||||
|
@Libelles.RoleAuteur(role)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Complement>
|
||||||
|
</SaisieListe>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="champ">
|
||||||
|
<legend class="champ-libelle">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierFormat" />
|
||||||
|
<span>Format</span>
|
||||||
|
</label>
|
||||||
|
</legend>
|
||||||
|
@if (Saisie.ModifierFormat)
|
||||||
|
{
|
||||||
|
<div class="segments">
|
||||||
|
@foreach (var format in Formats)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="segment @(Saisie.Format == format ? "segment-actif" : null)"
|
||||||
|
@onclick="() => Saisie.Format = format">
|
||||||
|
@Libelles.Format(format)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="champ">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierEditeur" />
|
||||||
|
<span>Éditeur</span>
|
||||||
|
</label>
|
||||||
|
@if (Saisie.ModifierEditeur)
|
||||||
|
{
|
||||||
|
<input class="champ-saisie" type="text" @bind="Saisie.Editeur" @bind:event="oninput"
|
||||||
|
autocomplete="off" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="champ">
|
||||||
|
<legend class="champ-libelle">
|
||||||
|
<label class="bascule">
|
||||||
|
<input type="checkbox" @bind="Saisie.ModifierStatut" />
|
||||||
|
<span>Statut de lecture <span class="champ-aide">(le vôtre)</span></span>
|
||||||
|
</label>
|
||||||
|
</legend>
|
||||||
|
@if (Saisie.ModifierStatut)
|
||||||
|
{
|
||||||
|
<ChoixStatut Valeur="Saisie.Statut" ValeurChanged="s => Saisie.Statut = s" />
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Erreur))
|
||||||
|
{
|
||||||
|
<p class="message-erreur" role="alert">@Erreur</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(MessageBlocage))
|
||||||
|
{
|
||||||
|
<p class="message-avertissement" role="status">@MessageBlocage</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="actions-formulaire">
|
||||||
|
<button type="button" class="bouton bouton-principal"
|
||||||
|
disabled="@(EnCours || Bloque || Saisie.AucunChamp)"
|
||||||
|
title="@(Saisie.AucunChamp ? "Cochez au moins un champ à modifier." : MessageBlocage)"
|
||||||
|
@onclick="Valider">
|
||||||
|
@(EnCours ? "Enregistrement…" : LibelleValidation)
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (OnAnnuler.HasDelegate)
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret" disabled="@EnCours"
|
||||||
|
@onclick="OnAnnuler">
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private static readonly Format[] Formats = Enum.GetValues<Format>();
|
||||||
|
|
||||||
|
private static readonly TypeDocument[] Types = Enum.GetValues<TypeDocument>();
|
||||||
|
|
||||||
|
private static readonly RoleAuteur[] Roles = Enum.GetValues<RoleAuteur>();
|
||||||
|
|
||||||
|
private void PoserRole(int index, RoleAuteur role) =>
|
||||||
|
Saisie.Auteurs[index] = Saisie.Auteurs[index] with { Role = role };
|
||||||
|
|
||||||
|
/// <summary>Voir la remarque équivalente sur FormulaireLivre.razor : référence stable tant
|
||||||
|
/// que la saisie ne change pas, sans quoi SaisieListe se remet à zéro à chaque frappe.</summary>
|
||||||
|
private NomsAuteurs? _noms;
|
||||||
|
|
||||||
|
private NomsAuteurs Noms => _noms ??= new NomsAuteurs(Saisie.Auteurs);
|
||||||
|
|
||||||
|
private SaisieLot? _saisiePrecedente;
|
||||||
|
|
||||||
|
private IReadOnlyList<string> _themesConnus = [];
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_themesConnus =
|
||||||
|
[
|
||||||
|
.. (await Api.ListerAsync(new CritereLivres()))
|
||||||
|
.SelectMany(l => l.Themes)
|
||||||
|
.DistinctBy(NormalisationTexte.Normaliser, StringComparer.Ordinal)
|
||||||
|
.OrderBy(NormalisationTexte.Normaliser, StringComparer.Ordinal),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_themesConnus = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (!ReferenceEquals(_saisiePrecedente, Saisie))
|
||||||
|
{
|
||||||
|
_saisiePrecedente = Saisie;
|
||||||
|
_noms = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Parameter, EditorRequired] public SaisieLot Saisie { get; set; } = new();
|
||||||
|
|
||||||
|
[Parameter] public int NombreLivres { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public string LibelleValidation { get; set; } = "Appliquer";
|
||||||
|
|
||||||
|
[Parameter] public string? Erreur { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public bool EnCours { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public string? MessageBlocage { get; set; }
|
||||||
|
|
||||||
|
private bool Bloque => !string.IsNullOrEmpty(MessageBlocage);
|
||||||
|
|
||||||
|
[Parameter] public EventCallback OnValider { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public EventCallback OnAnnuler { get; set; }
|
||||||
|
|
||||||
|
private Task Valider() => OnValider.InvokeAsync();
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
Un bloc de série au catalogue, replié ou déplié — et récursif, parce qu'un cycle est une
|
Un bloc de série au catalogue, replié ou déplié — et récursif, parce qu'un cycle est une
|
||||||
série de séries : chaque niveau porte sa propre bascule.
|
série de séries : chaque niveau porte sa propre bascule.
|
||||||
|
|
||||||
⚠️ Tout est DÉPLIÉ par défaut, et l'on replie ce dont on ne veut pas. C'est la règle déjà
|
⚠️ Tout est REPLIÉ par défaut ici (2026-09-10), à l'inverse de l'arbre des séries : le
|
||||||
tenue par l'arbre des séries : l'inverse obligerait à ouvrir chaque bloc pour retrouver un
|
catalogue liste D'ABORD les livres, une série n'y est qu'un détail qu'on ouvre au besoin —
|
||||||
livre qu'on voyait très bien avant.
|
alors que l'écran des séries EST la liste des séries, où les masquer toutes viderait l'écran.
|
||||||
|
|
||||||
⚠️ Replié, le bloc annonce toujours ce qu'il contient : sans ce compte, le compteur du haut
|
⚠️ Replié, le bloc annonce toujours ce qu'il contient : sans ce compte, le compteur du haut
|
||||||
du catalogue paraîtrait mentir.
|
du catalogue paraîtrait mentir.
|
||||||
@@ -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" Replies="Replies" Totaux="Totaux" />
|
<GroupeCatalogue Entree="sous" Ouverts="Ouverts" Totaux="Totaux"
|
||||||
|
LotActif="LotActif" SelectionLot="SelectionLot" OnSelectionner="OnSelectionner" />
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -53,9 +54,24 @@
|
|||||||
<ul class="liste-livres">
|
<ul class="liste-livres">
|
||||||
@foreach (var livre in Entree.Livres)
|
@foreach (var livre in Entree.Livres)
|
||||||
{
|
{
|
||||||
<li @key="livre.Id">
|
@if (LotActif)
|
||||||
<CarteLivre Livre="livre" />
|
{
|
||||||
</li>
|
<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">
|
||||||
|
<CarteLivre Livre="livre" />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -66,15 +82,14 @@
|
|||||||
[Parameter, EditorRequired] public EntreeCatalogue Entree { get; set; } = default!;
|
[Parameter, EditorRequired] public EntreeCatalogue Entree { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Blocs REPLIÉS, par identifiant de série — et non dépliés : le défaut est de tout montrer.
|
/// Blocs OUVERTS, par identifiant de série — et non repliés : le défaut est de tout replier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// État local de l'écran, volontairement non persistant, comme celui de l'arbre des séries :
|
/// État local de l'écran, volontairement non persistant : c'est un confort de lecture, pas
|
||||||
/// c'est un confort de lecture, pas une propriété de la série. Il survit en revanche aux
|
/// une propriété de la série. Il survit en revanche aux rechargements du catalogue (frappe
|
||||||
/// rechargements du catalogue (frappe dans la recherche, filtre), le jeu appartenant à
|
/// dans la recherche, filtre), le jeu appartenant à l'écran et non au bloc.
|
||||||
/// l'écran et non au bloc.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Parameter, EditorRequired] public ISet<int> Replies { get; set; } = new HashSet<int>();
|
[Parameter, EditorRequired] public ISet<int> Ouverts { get; set; } = new HashSet<int>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compteurs par série, descendance comprise : ce qu'on possède, et ce que la série recense.
|
/// Compteurs par série, descendance comprise : ce qu'on possède, et ce que la série recense.
|
||||||
@@ -86,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
|
||||||
@@ -95,13 +123,13 @@
|
|||||||
|
|
||||||
private int Total => Compte.Total;
|
private int Total => Compte.Total;
|
||||||
|
|
||||||
private bool Deplie => !Replies.Contains(Entree.Serie!.Id);
|
private bool Deplie => Ouverts.Contains(Entree.Serie!.Id);
|
||||||
|
|
||||||
private void Basculer()
|
private void Basculer()
|
||||||
{
|
{
|
||||||
if (!Replies.Add(Entree.Serie!.Id))
|
if (!Ouverts.Add(Entree.Serie!.Id))
|
||||||
{
|
{
|
||||||
Replies.Remove(Entree.Serie!.Id);
|
Ouverts.Remove(Entree.Serie!.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public static class Libelles
|
|||||||
MaBibli.Shared.Entites.TypeDocument.NonPrecise => "Non précisé",
|
MaBibli.Shared.Entites.TypeDocument.NonPrecise => "Non précisé",
|
||||||
MaBibli.Shared.Entites.TypeDocument.Roman => "Roman",
|
MaBibli.Shared.Entites.TypeDocument.Roman => "Roman",
|
||||||
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "BD",
|
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "BD",
|
||||||
|
MaBibli.Shared.Entites.TypeDocument.Pratique => "Pratique",
|
||||||
|
MaBibli.Shared.Entites.TypeDocument.Connaissance => "Connaissance",
|
||||||
_ => type.ToString(),
|
_ => type.ToString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,6 +66,8 @@ public static class Libelles
|
|||||||
{
|
{
|
||||||
MaBibli.Shared.Entites.TypeDocument.Roman => "type-roman",
|
MaBibli.Shared.Entites.TypeDocument.Roman => "type-roman",
|
||||||
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "type-bd",
|
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "type-bd",
|
||||||
|
MaBibli.Shared.Entites.TypeDocument.Pratique => "type-pratique",
|
||||||
|
MaBibli.Shared.Entites.TypeDocument.Connaissance => "type-connaissance",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using MaBibli.Shared.Dtos;
|
||||||
|
using MaBibli.Shared.Entites;
|
||||||
|
|
||||||
|
namespace MaBibli.Client.Composants;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saisie du panneau « Modifier en lot » (fiche série).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// ⚠️ Chaque champ porte sa propre case <c>Modifier…</c> : sans elle, la valeur par défaut d'un
|
||||||
|
/// champ (« NonPrecise », « Physique »…) est déjà une vraie valeur, et l'appliquer à tous les
|
||||||
|
/// livres sélectionnés écraserait un champ qu'on ne voulait pas toucher. Seuls les champs cochés
|
||||||
|
/// sont envoyés ; les autres livres restent inchangés sur les champs non cochés (CLAUDE.md,
|
||||||
|
/// invariant 10 : on n'affiche — et n'applique — que ce qui a été choisi).
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class SaisieLot
|
||||||
|
{
|
||||||
|
public bool ModifierType { get; set; }
|
||||||
|
|
||||||
|
public TypeDocument TypeDocument { get; set; }
|
||||||
|
|
||||||
|
public bool ModifierThemes { get; set; }
|
||||||
|
|
||||||
|
public List<string> Themes { get; set; } = [];
|
||||||
|
|
||||||
|
public bool ModifierAuteurs { get; set; }
|
||||||
|
|
||||||
|
public List<AuteurSaisi> Auteurs { get; set; } = [];
|
||||||
|
|
||||||
|
public bool ModifierFormat { get; set; }
|
||||||
|
|
||||||
|
public Format Format { get; set; }
|
||||||
|
|
||||||
|
public bool ModifierEditeur { get; set; }
|
||||||
|
|
||||||
|
public string? Editeur { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Statut de lecture <b>de l'appelant</b>, comme partout ailleurs : appliquer un statut en
|
||||||
|
/// lot pose la lecture de la personne qui agit, jamais celle d'un tiers.
|
||||||
|
/// </summary>
|
||||||
|
public bool ModifierStatut { get; set; }
|
||||||
|
|
||||||
|
public Statut? Statut { get; set; }
|
||||||
|
|
||||||
|
public bool AucunChamp =>
|
||||||
|
!ModifierType && !ModifierThemes && !ModifierAuteurs
|
||||||
|
&& !ModifierFormat && !ModifierEditeur && !ModifierStatut;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
@page "/a-propos"
|
@page "/a-propos"
|
||||||
@inject ServiceLivresApi Api
|
@inject ServiceLivresApi Api
|
||||||
@inject EtatReseau Reseau
|
@inject EtatReseau Reseau
|
||||||
|
@inject IJSRuntime JS
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
@*
|
@*
|
||||||
@@ -93,6 +94,34 @@
|
|||||||
celle qui y tourne en ce moment.
|
celle qui y tourne en ce moment.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@*
|
||||||
|
⚠️ Ce numéro vient du SERVEUR (voir plus haut) : il ne prouve pas que le navigateur a
|
||||||
|
lui-même récupéré la nouvelle version. Le service worker n'active un nouveau cache
|
||||||
|
qu'une fois TOUS les onglets fermés — un appareil qui reste ouvert en arrière-plan
|
||||||
|
(téléphone surtout) peut rester bloqué sur l'ancienne version indéfiniment. Ce bouton
|
||||||
|
court-circuite cette attente : désinscrire le worker et vider son cache, recharger.
|
||||||
|
Hors-ligne il est désactivé — le déclencher sans réseau couperait l'accès hors-ligne
|
||||||
|
en cours, sans rien pouvoir retélécharger à la place.
|
||||||
|
*@
|
||||||
|
<p>
|
||||||
|
@if (Reseau.EnLigne)
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret" disabled="@_forcageEnCours"
|
||||||
|
@onclick="ForcerMiseAJourAsync">
|
||||||
|
@(_forcageEnCours ? "Mise à jour…" : "Forcer la mise à jour")
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret" disabled
|
||||||
|
title="@EtatReseau.MotifHorsLigne">Forcer la mise à jour</button>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<p class="message-discret">
|
||||||
|
Vide le cache de l'application et la recharge entièrement — à utiliser si une nouvelle
|
||||||
|
version tarde à apparaître malgré la fermeture de tous les onglets.
|
||||||
|
</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -145,6 +174,7 @@
|
|||||||
private const string Depot = "https://git.akbar.nohost.me/mathieu/mabibli";
|
private const string Depot = "https://git.akbar.nohost.me/mathieu/mabibli";
|
||||||
|
|
||||||
private VersionApplication? _version;
|
private VersionApplication? _version;
|
||||||
|
private bool _forcageEnCours;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -161,6 +191,28 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Désinscrit le service worker et vide son cache avant de recharger — voir js/forcer-maj.js.
|
||||||
|
/// Le bouton reste désactivé pendant l'opération : un double clic déclencherait deux
|
||||||
|
/// désinscriptions concurrentes pour un seul rechargement.
|
||||||
|
/// </summary>
|
||||||
|
private async Task ForcerMiseAJourAsync()
|
||||||
|
{
|
||||||
|
_forcageEnCours = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/forcer-maj.js");
|
||||||
|
await module.InvokeVoidAsync("forcerMiseAJour");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Le rechargement forcé qui suit normalement l'appel JS ne survient pas ici : on
|
||||||
|
// rend la main pour que l'utilisateur puisse réessayer plutôt que de rester sur un
|
||||||
|
// bouton grisé pour rien.
|
||||||
|
_forcageEnCours = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string Quand(DateTimeOffset instant)
|
private static string Quand(DateTimeOffset instant)
|
||||||
{
|
{
|
||||||
var local = instant.ToLocalTime();
|
var local = instant.ToLocalTime();
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="onglets-secondaires" role="group" aria-label="Manière de chercher">
|
<div class="onglets-secondaires" role="group" aria-label="Manière de chercher">
|
||||||
<button type="button" class="segment @(_mode == Mode.Titre ? "segment-actif" : null)"
|
|
||||||
@onclick="() => Basculer(Mode.Titre)">Par titre</button>
|
|
||||||
<button type="button" class="segment @(_mode == Mode.Isbn ? "segment-actif" : null)"
|
<button type="button" class="segment @(_mode == Mode.Isbn ? "segment-actif" : null)"
|
||||||
@onclick="() => Basculer(Mode.Isbn)">Par ISBN</button>
|
@onclick="() => Basculer(Mode.Isbn)">Par ISBN</button>
|
||||||
|
<button type="button" class="segment @(_mode == Mode.Titre ? "segment-actif" : null)"
|
||||||
|
@onclick="() => Basculer(Mode.Titre)">Par titre</button>
|
||||||
<button type="button" class="segment @(_mode == Mode.Manuel ? "segment-actif" : null)"
|
<button type="button" class="segment @(_mode == Mode.Manuel ? "segment-actif" : null)"
|
||||||
@onclick="() => Basculer(Mode.Manuel)">À la main</button>
|
@onclick="() => Basculer(Mode.Manuel)">À la main</button>
|
||||||
<button type="button" class="segment @(_mode == Mode.Revue ? "segment-actif" : null)"
|
<button type="button" class="segment @(_mode == Mode.Revue ? "segment-actif" : null)"
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
@code {
|
@code {
|
||||||
private enum Mode { Titre, Isbn, Manuel, Revue }
|
private enum Mode { Titre, Isbn, Manuel, Revue }
|
||||||
|
|
||||||
private Mode _mode = Mode.Titre;
|
private Mode _mode = Mode.Isbn;
|
||||||
private ElementReference _premierChamp;
|
private ElementReference _premierChamp;
|
||||||
private bool _focusAFaire = true;
|
private bool _focusAFaire = true;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,28 @@
|
|||||||
Filtrer@(FiltresActifs > 0 ? $" ({FiltresActifs})" : "")
|
Filtrer@(FiltresActifs > 0 ? $" ({FiltresActifs})" : "")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@if (_grouper && Entrees.Any(e => e.EstGroupe))
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret bouton-compact"
|
||||||
|
@onclick="() => { if (AuMoinsUneSerieOuverte) { ToutReplier(); } else { ToutDeplier(); } }">
|
||||||
|
@(AuMoinsUneSerieOuverte ? "Replier les séries" : "Déplier les séries")
|
||||||
|
</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>
|
||||||
@@ -206,7 +228,7 @@
|
|||||||
|
|
||||||
@if (_chargement && _livres is null)
|
@if (_chargement && _livres is null)
|
||||||
{
|
{
|
||||||
<p class="message-discret">Chargement…</p>
|
<Patience Message="Chargement du catalogue…" />
|
||||||
}
|
}
|
||||||
else if (_livres is { Count: 0 })
|
else if (_livres is { Count: 0 })
|
||||||
{
|
{
|
||||||
@@ -218,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
|
||||||
@@ -228,13 +291,32 @@ else if (_livres is not null)
|
|||||||
tomes occupait tout l'écran alors qu'on cherchait un livre rangé après lui. Replier ne
|
tomes occupait tout l'écran alors qu'on cherchait un livre rangé après lui. Replier ne
|
||||||
retire rien de la liste — le compteur du haut ne bouge pas, et le bloc replié dit
|
retire rien de la liste — le compteur du haut ne bouge pas, et le bloc replié dit
|
||||||
toujours ce qu'il contient.
|
toujours ce qu'il contient.
|
||||||
|
|
||||||
|
⚠️ Ici, contrairement à l'arbre des séries, tout est REPLIÉ par défaut (2026-09-10) :
|
||||||
|
le catalogue liste des livres, pas des séries — une série n'y est qu'un regroupement
|
||||||
|
qu'on ouvre au besoin. Le bouton qui bascule tout l'arbre est dans l'en-tête, à côté
|
||||||
|
de « Filtrer », pas ici : c'est un réglage d'affichage, pas une action sur la liste.
|
||||||
*@
|
*@
|
||||||
<ul class="liste-livres">
|
<ul class="liste-livres">
|
||||||
@foreach (var entree in Entrees)
|
@foreach (var entree in Entrees)
|
||||||
{
|
{
|
||||||
@if (entree.EstGroupe)
|
@if (entree.EstGroupe)
|
||||||
{
|
{
|
||||||
<GroupeCatalogue Entree="entree" Replies="_replies" 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
|
||||||
{
|
{
|
||||||
@@ -269,15 +351,45 @@ else if (_livres is not null)
|
|||||||
private bool _grouper = true;
|
private bool _grouper = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Blocs repliés à la main, par identifiant de série. Tout est déplié par défaut.
|
/// Blocs ouverts à la main, par identifiant de série. Tout est replié par défaut.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// ⚠️ Le jeu appartient à l'ÉCRAN, et non aux blocs : le catalogue se relit à chaque frappe
|
/// ⚠️ Le jeu appartient à l'ÉCRAN, et non aux blocs : le catalogue se relit à chaque frappe
|
||||||
/// dans la recherche, et un état porté par les composants se perdrait à chaque caractère.
|
/// dans la recherche, et un état porté par les composants se perdrait à chaque caractère.
|
||||||
/// Il n'est pas persistant d'une visite à l'autre pour autant — c'est un confort de
|
/// Il n'est pas persistant d'une visite à l'autre pour autant — c'est un confort de
|
||||||
/// lecture, pas une propriété de la série, même règle que l'arbre des séries.
|
/// lecture, pas une propriété de la série.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private readonly HashSet<int> _replies = [];
|
private readonly HashSet<int> _ouverts = [];
|
||||||
|
|
||||||
|
private bool AuMoinsUneSerieOuverte => _ouverts.Count > 0;
|
||||||
|
|
||||||
|
private void ToutReplier() => _ouverts.Clear();
|
||||||
|
|
||||||
|
private void ToutDeplier()
|
||||||
|
{
|
||||||
|
foreach (var id in IdentifiantsSeries(Entrees))
|
||||||
|
{
|
||||||
|
_ouverts.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<int> IdentifiantsSeries(IEnumerable<EntreeCatalogue> entrees)
|
||||||
|
{
|
||||||
|
foreach (var entree in entrees)
|
||||||
|
{
|
||||||
|
if (!entree.EstGroupe)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return entree.Serie!.Id;
|
||||||
|
|
||||||
|
foreach (var id in IdentifiantsSeries(entree.SousEntrees))
|
||||||
|
{
|
||||||
|
yield return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ce que chaque série possède et recense, descendance comprise.
|
/// Ce que chaque série possède et recense, descendance comprise.
|
||||||
@@ -305,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>
|
||||||
@@ -563,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;
|
||||||
|
|||||||
@@ -102,52 +102,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (ModeEdition)
|
|
||||||
{
|
|
||||||
<h2 class="titre-section">La revue</h2>
|
|
||||||
|
|
||||||
<label class="champ">
|
|
||||||
<span class="champ-libelle">Nom</span>
|
|
||||||
<input class="champ-saisie" type="text" @bind="_titre" @bind:event="oninput" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="champ">
|
|
||||||
<span class="champ-libelle">
|
|
||||||
ISSN <span class="champ-aide">(identifiant de la revue ; le tiret est posé à l'enregistrement)</span>
|
|
||||||
</span>
|
|
||||||
<input class="champ-saisie" type="text" @bind="_issn" @bind:event="oninput"
|
|
||||||
placeholder="2466-6718" autocomplete="off" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="champ">
|
|
||||||
<span class="champ-libelle">Éditeur</span>
|
|
||||||
<input class="champ-saisie" type="text" @bind="_editeur" @bind:event="oninput" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="actions-formulaire">
|
|
||||||
<button type="button" class="bouton bouton-principal"
|
|
||||||
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
|
|
||||||
title="@MotifBlocage"
|
|
||||||
@onclick="EnregistrerAsync">
|
|
||||||
Enregistrer
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button" class="bouton bouton-discret bouton-danger"
|
|
||||||
disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
|
|
||||||
@onclick="SupprimerAsync">
|
|
||||||
@(_confirmeSuppression ? "Confirmer la suppression" : "Supprimer la revue")
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (_confirmeSuppression)
|
|
||||||
{
|
|
||||||
<p class="message-avertissement" role="status">
|
|
||||||
Les @_revue.Numeros.Count numéro(s) recensés disparaîtront avec elle : ils
|
|
||||||
n'existent que par leur revue.
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<h2 class="titre-section">Numéros possédés</h2>
|
<h2 class="titre-section">Numéros possédés</h2>
|
||||||
|
|
||||||
@if (_revue.Numeros.Count == 0)
|
@if (_revue.Numeros.Count == 0)
|
||||||
@@ -297,6 +251,52 @@ else
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (ModeEdition)
|
||||||
|
{
|
||||||
|
<h2 class="titre-section">La revue</h2>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">Nom</span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_titre" @bind:event="oninput" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">
|
||||||
|
ISSN <span class="champ-aide">(identifiant de la revue ; le tiret est posé à l'enregistrement)</span>
|
||||||
|
</span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_issn" @bind:event="oninput"
|
||||||
|
placeholder="2466-6718" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">Éditeur</span>
|
||||||
|
<input class="champ-saisie" type="text" @bind="_editeur" @bind:event="oninput" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="actions-formulaire">
|
||||||
|
<button type="button" class="bouton bouton-principal"
|
||||||
|
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
|
||||||
|
title="@MotifBlocage"
|
||||||
|
@onclick="EnregistrerAsync">
|
||||||
|
Enregistrer
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="bouton bouton-discret bouton-danger"
|
||||||
|
disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
|
||||||
|
@onclick="SupprimerAsync">
|
||||||
|
@(_confirmeSuppression ? "Confirmer la suppression" : "Supprimer la revue")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (_confirmeSuppression)
|
||||||
|
{
|
||||||
|
<p class="message-avertissement" role="status">
|
||||||
|
Les @_revue.Numeros.Count numéro(s) recensés disparaîtront avec elle : ils
|
||||||
|
n'existent que par leur revue.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ else
|
|||||||
var total = Total(_serie);
|
var total = Total(_serie);
|
||||||
<text>@total.Possedes sur @total.Total tomes dans la bibliothèque.</text>
|
<text>@total.Possedes sur @total.Total tomes dans la bibliothèque.</text>
|
||||||
}
|
}
|
||||||
|
@if (_serie.Etat != EtatSerie.EnCours)
|
||||||
|
{
|
||||||
|
<text> — Série @(_serie.Etat == EtatSerie.Terminee ? "terminée" : "abandonnée").</text>
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,6 +78,20 @@ else
|
|||||||
{
|
{
|
||||||
<a class="bouton bouton-compact bouton-discret" href="@($"series/{Id}/ordre")">Changer l'ordre</a>
|
<a class="bouton bouton-compact bouton-discret" href="@($"series/{Id}/ordre")">Changer l'ordre</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@*
|
||||||
|
« Modifier en lot » n'apparaît qu'à partir de DEUX tomes possédés : sur un
|
||||||
|
seul, l'écran de la fiche fait aussi bien. Même seuil que « Changer l'ordre ».
|
||||||
|
*@
|
||||||
|
@if (TomesModifiablesEnLot > 1)
|
||||||
|
{
|
||||||
|
<button type="button" class="bouton bouton-compact bouton-discret"
|
||||||
|
aria-expanded="@Aria.Etat(_lotOuvert)" aria-controls="lot-tomes"
|
||||||
|
@onclick="BasculerLot">
|
||||||
|
Modifier en lot
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
<a class="bouton bouton-compact bouton-principal" href="@($"series/{Id}/edition")">Modifier</a>
|
<a class="bouton bouton-compact bouton-principal" href="@($"series/{Id}/edition")">Modifier</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -332,6 +350,38 @@ else
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@*
|
||||||
|
Modification en lot : on coche des tomes possédés, puis on choisit ce qu'on leur
|
||||||
|
applique. Même patron que la sélection de la bibliographie (barre en haut, collante),
|
||||||
|
sauf que l'action n'est pas un simple bouton mais un formulaire — il reste donc
|
||||||
|
affiché sous la barre plutôt que de se déclencher au clic.
|
||||||
|
*@
|
||||||
|
@if (LotActif)
|
||||||
|
{
|
||||||
|
<div id="lot-tomes">
|
||||||
|
@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="@_enCours" @onclick="() => _selectionLot.Clear()">
|
||||||
|
Tout décocher
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormulaireLotLivres Saisie="_saisieLot" NombreLivres="_selectionLot.Count"
|
||||||
|
Erreur="@_erreur" EnCours="_enCours" MessageBlocage="@MotifBlocage"
|
||||||
|
OnValider="EnregistrerLotAsync" OnAnnuler="AnnulerLot" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="message-discret">Cochez les tomes à modifier ensemble.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@*
|
@*
|
||||||
⚠️ L'ordre est STOCKÉ, jamais déduit d'une année de publication : une préquelle se
|
⚠️ L'ordre est STOCKÉ, jamais déduit d'une année de publication : une préquelle se
|
||||||
lit avant le livre paru dix ans plus tôt. Les flèches sont le seul moyen de le
|
lit avant le livre paru dix ans plus tôt. Les flèches sont le seul moyen de le
|
||||||
@@ -342,6 +392,17 @@ else
|
|||||||
@foreach (var (element, index) in TomesAffiches.Select((e, i) => (e, i)))
|
@foreach (var (element, index) in TomesAffiches.Select((e, i) => (e, i)))
|
||||||
{
|
{
|
||||||
<li class="carte-tome @(element.Possede ? null : "tome-manquant")" @key="element.Id">
|
<li class="carte-tome @(element.Possede ? null : "tome-manquant")" @key="element.Id">
|
||||||
|
@if (LotActif && element.LivreId is { } livreLot
|
||||||
|
&& _livresCatalogue.Any(l => l.Id == livreLot))
|
||||||
|
{
|
||||||
|
<label class="oeuvre-case">
|
||||||
|
<input type="checkbox"
|
||||||
|
checked="@_selectionLot.Contains(livreLot)"
|
||||||
|
@onchange="e => SelectionnerLot(livreLot, e.Value is true)" />
|
||||||
|
<span class="lecture-ecran">Sélectionner « @element.Titre »</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="rang-actions">
|
<div class="rang-actions">
|
||||||
@if (ModeOrdre)
|
@if (ModeOrdre)
|
||||||
{
|
{
|
||||||
@@ -415,14 +476,6 @@ else
|
|||||||
<div class="actions-tome">
|
<div class="actions-tome">
|
||||||
@if (!element.Possede)
|
@if (!element.Possede)
|
||||||
{
|
{
|
||||||
@* Le tome manquant est COMMUN au foyer, l'envie qu'il devient est
|
|
||||||
PERSONNELLE : deux membres peuvent vouloir le même tome. *@
|
|
||||||
<button type="button" class="bouton bouton-compact"
|
|
||||||
disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
|
|
||||||
@onclick="() => SouhaiterAsync(element)">
|
|
||||||
Ajouter à mes envies
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button" class="bouton bouton-compact"
|
<button type="button" class="bouton bouton-compact"
|
||||||
disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
|
disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
|
||||||
@onclick="() => OuvrirRattachement(element.Id)">
|
@onclick="() => OuvrirRattachement(element.Id)">
|
||||||
@@ -463,6 +516,7 @@ else
|
|||||||
<div class="barre-recherche">
|
<div class="barre-recherche">
|
||||||
<input class="champ-saisie" type="text"
|
<input class="champ-saisie" type="text"
|
||||||
placeholder="7, Hors-série, 3.5…"
|
placeholder="7, Hors-série, 3.5…"
|
||||||
|
@ref="_champNumero"
|
||||||
@bind="_numeroSaisi" @bind:event="oninput"
|
@bind="_numeroSaisi" @bind:event="oninput"
|
||||||
@onkeydown="@(e => SurToucheNumero(e, element))"
|
@onkeydown="@(e => SurToucheNumero(e, element))"
|
||||||
aria-label="@($"Numéro du tome « {element.Titre} »")"
|
aria-label="@($"Numéro du tome « {element.Titre} »")"
|
||||||
@@ -590,6 +644,15 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label class="champ">
|
||||||
|
<span class="champ-libelle">État</span>
|
||||||
|
<select class="champ-saisie" @bind="_etat">
|
||||||
|
<option value="@EtatSerie.EnCours">En cours — d'autres tomes à venir</option>
|
||||||
|
<option value="@EtatSerie.Terminee">Terminée — pas d'autres tomes</option>
|
||||||
|
<option value="@EtatSerie.Abandonnee">Abandonnée — arrêtée sans conclusion</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="actions-formulaire">
|
<div class="actions-formulaire">
|
||||||
<button type="button" class="bouton bouton-principal"
|
<button type="button" class="bouton bouton-principal"
|
||||||
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
|
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
|
||||||
@@ -641,8 +704,17 @@ else
|
|||||||
private bool _enCours;
|
private bool _enCours;
|
||||||
private string? _erreur;
|
private string? _erreur;
|
||||||
|
|
||||||
|
/// <summary>Le panneau de modification en lot est déplié.</summary>
|
||||||
|
private bool _lotOuvert;
|
||||||
|
|
||||||
|
/// <summary>Identifiants des LIVRES (pas des places) cochés pour la modification en lot.</summary>
|
||||||
|
private readonly HashSet<int> _selectionLot = [];
|
||||||
|
|
||||||
|
private SaisieLot _saisieLot = new();
|
||||||
|
|
||||||
private string _titre = string.Empty;
|
private string _titre = string.Empty;
|
||||||
private int _parenteId;
|
private int _parenteId;
|
||||||
|
private EtatSerie _etat = EtatSerie.EnCours;
|
||||||
private bool _confirmeSuppression;
|
private bool _confirmeSuppression;
|
||||||
|
|
||||||
private string _nouveauTome = string.Empty;
|
private string _nouveauTome = string.Empty;
|
||||||
@@ -680,6 +752,11 @@ else
|
|||||||
|
|
||||||
private string _numeroSaisi = string.Empty;
|
private string _numeroSaisi = string.Empty;
|
||||||
|
|
||||||
|
private ElementReference _champNumero;
|
||||||
|
|
||||||
|
/// <summary>Le champ « numéro » vient de s'ouvrir : à focaliser au prochain rendu.</summary>
|
||||||
|
private bool _focusNumero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trier par numéro de tome plutôt que par ordre de lecture.
|
/// Trier par numéro de tome plutôt que par ordre de lecture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -765,6 +842,11 @@ else
|
|||||||
{
|
{
|
||||||
Ouvrir(VoieAjout.Aucune);
|
Ouvrir(VoieAjout.Aucune);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Les deux panneaux se marcheraient dessus sur un petit écran : un seul à la fois.
|
||||||
|
_lotOuvert = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Vrai quand ce qui est tapé dans la saisie manuelle est un code et non un titre.</summary>
|
/// <summary>Vrai quand ce qui est tapé dans la saisie manuelle est un code et non un titre.</summary>
|
||||||
@@ -799,6 +881,12 @@ else
|
|||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool premierRendu)
|
protected override async Task OnAfterRenderAsync(bool premierRendu)
|
||||||
{
|
{
|
||||||
|
if (_focusNumero)
|
||||||
|
{
|
||||||
|
_focusNumero = false;
|
||||||
|
await _champNumero.FocusAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (_focusAFaire is not { } voie)
|
if (_focusAFaire is not { } voie)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -824,6 +912,7 @@ else
|
|||||||
{
|
{
|
||||||
_numeroOuvert = element.Id;
|
_numeroOuvert = element.Id;
|
||||||
_numeroSaisi = element.Numero ?? string.Empty;
|
_numeroSaisi = element.Numero ?? string.Empty;
|
||||||
|
_focusNumero = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SurToucheNumero(KeyboardEventArgs e, ElementSerieDto element)
|
private async Task SurToucheNumero(KeyboardEventArgs e, ElementSerieDto element)
|
||||||
@@ -860,6 +949,116 @@ else
|
|||||||
/// <summary>Ni édition de la fiche, ni réordonnancement : l'écran qu'on ouvre pour regarder.</summary>
|
/// <summary>Ni édition de la fiche, ni réordonnancement : l'écran qu'on ouvre pour regarder.</summary>
|
||||||
private bool ModeConsultation => !ModeEdition && !ModeOrdre;
|
private bool ModeConsultation => !ModeEdition && !ModeOrdre;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Le panneau de lot n'a de sens qu'en consultation : la case à cocher se confondrait avec
|
||||||
|
/// les flèches de l'écran d'ordre, qui reste dédié au déplacement des tomes.
|
||||||
|
/// </summary>
|
||||||
|
private bool LotActif => _lotOuvert && ModeConsultation;
|
||||||
|
|
||||||
|
/// <summary>Tomes de la série effectivement rattachés à un livre du catalogue local.</summary>
|
||||||
|
private int TomesModifiablesEnLot =>
|
||||||
|
_serie?.Elements.Count(e => e.LivreId is { } id && _livresCatalogue.Any(l => l.Id == id)) ?? 0;
|
||||||
|
|
||||||
|
/// <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 <see cref="BasculerAjout"/> : un panneau qui se
|
||||||
|
/// rouvrirait sur l'état d'avant laisserait croire à une saisie encore active.
|
||||||
|
/// </summary>
|
||||||
|
private void BasculerLot()
|
||||||
|
{
|
||||||
|
_lotOuvert = !_lotOuvert;
|
||||||
|
_erreur = null;
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
|
||||||
|
// Les deux panneaux se marcheraient dessus sur un petit écran : un seul à la fois.
|
||||||
|
if (_lotOuvert)
|
||||||
|
{
|
||||||
|
_ajoutOuvert = false;
|
||||||
|
Ouvrir(VoieAjout.Aucune);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectionnerLot(int livreId, bool selectionne)
|
||||||
|
{
|
||||||
|
if (selectionne)
|
||||||
|
{
|
||||||
|
_selectionLot.Add(livreId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectionLot.Remove(livreId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnnulerLot()
|
||||||
|
{
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
_erreur = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applique la saisie en lot à chacun des livres cochés, un appel à la fois — même patron que
|
||||||
|
/// <c>MasquerSelectionAsync</c> de la bibliographie : 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()
|
||||||
|
{
|
||||||
|
_enCours = true;
|
||||||
|
_erreur = null;
|
||||||
|
|
||||||
|
var erreurs = new List<string>();
|
||||||
|
|
||||||
|
foreach (var livreId in _selectionLot)
|
||||||
|
{
|
||||||
|
var livre = _livresCatalogue.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!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_enCours = false;
|
||||||
|
|
||||||
|
if (erreurs.Count > 0)
|
||||||
|
{
|
||||||
|
_erreur = string.Join(" ", erreurs.Distinct());
|
||||||
|
await ChargerAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lotOuvert = false;
|
||||||
|
_selectionLot.Clear();
|
||||||
|
_saisieLot = new SaisieLot();
|
||||||
|
_message = "Les livres sélectionnés ont été modifiés.";
|
||||||
|
await ChargerAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private bool Suffixe(string fin) =>
|
private bool Suffixe(string fin) =>
|
||||||
Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath.TrimEnd('/')
|
Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath.TrimEnd('/')
|
||||||
.EndsWith(fin, StringComparison.OrdinalIgnoreCase);
|
.EndsWith(fin, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -872,7 +1071,7 @@ else
|
|||||||
/// refuse aussi — cette liste évite seulement de proposer un choix qui sera rejeté.
|
/// refuse aussi — cette liste évite seulement de proposer un choix qui sera rejeté.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private IEnumerable<SerieDto> Candidates =>
|
private IEnumerable<SerieDto> Candidates =>
|
||||||
_toutes.Where(s => s.Id != Id && !EstDescendante(s.Id));
|
_toutes.Where(s => s.Id != Id && !EstDescendante(s.Id)).Reverse();
|
||||||
|
|
||||||
private bool EstDescendante(int candidate)
|
private bool EstDescendante(int candidate)
|
||||||
{
|
{
|
||||||
@@ -945,6 +1144,7 @@ else
|
|||||||
|
|
||||||
_titre = _serie.Titre;
|
_titre = _serie.Titre;
|
||||||
_parenteId = _serie.SerieParenteId ?? 0;
|
_parenteId = _serie.SerieParenteId ?? 0;
|
||||||
|
_etat = _serie.Etat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Remplace la série courante par celle que vient de rendre le serveur.</summary>
|
/// <summary>Remplace la série courante par celle que vient de rendre le serveur.</summary>
|
||||||
@@ -1340,12 +1540,16 @@ else
|
|||||||
private async Task EnregistrerAsync()
|
private async Task EnregistrerAsync()
|
||||||
{
|
{
|
||||||
await EcrireAsync(() => Api.ModifierSerieAsync(
|
await EcrireAsync(() => Api.ModifierSerieAsync(
|
||||||
Id, new EnregistrementSerie { Titre = _titre, SerieParenteId = _parenteId == 0 ? null : _parenteId }));
|
Id, new EnregistrementSerie
|
||||||
|
{
|
||||||
|
Titre = _titre,
|
||||||
|
SerieParenteId = _parenteId == 0 ? null : _parenteId,
|
||||||
|
Etat = _etat
|
||||||
|
}));
|
||||||
|
|
||||||
// Le rangement dans un cycle change la parenté des AUTRES séries affichées : on relit.
|
|
||||||
if (_erreur is null)
|
if (_erreur is null)
|
||||||
{
|
{
|
||||||
await ChargerAsync();
|
Navigation.NavigateTo($"series/{Id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ else
|
|||||||
|
|
||||||
/// <summary>Séries de premier niveau. Celles rangées dans un cycle s'affichent sous lui.</summary>
|
/// <summary>Séries de premier niveau. Celles rangées dans un cycle s'affichent sous lui.</summary>
|
||||||
private IEnumerable<SerieDto> Racines =>
|
private IEnumerable<SerieDto> Racines =>
|
||||||
_series?.Where(s => s.SerieParenteId is null) ?? [];
|
(_series?.Where(s => s.SerieParenteId is null) ?? []).OrderBy(s => s.Titre);
|
||||||
|
|
||||||
protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
|
protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ else
|
|||||||
<div class="actions-export">
|
<div class="actions-export">
|
||||||
@if (Reseau.EnLigne)
|
@if (Reseau.EnLigne)
|
||||||
{
|
{
|
||||||
|
<a class="bouton bouton-discret" href="souhaits/ajout">Ajouter une envie</a>
|
||||||
<a class="bouton bouton-discret" href="@ServiceLivresApi.UrlExportSouhaits(FormatExportSouhaits.Texte)" download>
|
<a class="bouton bouton-discret" href="@ServiceLivresApi.UrlExportSouhaits(FormatExportSouhaits.Texte)" download>
|
||||||
Exporter en texte
|
Exporter en texte
|
||||||
</a>
|
</a>
|
||||||
@@ -78,6 +79,8 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<button type="button" class="bouton bouton-discret" disabled
|
||||||
|
title="@EtatReseau.MotifHorsLigne">Ajouter une envie</button>
|
||||||
<button type="button" class="bouton bouton-discret" disabled
|
<button type="button" class="bouton bouton-discret" disabled
|
||||||
title="@MotifExport">Exporter en texte</button>
|
title="@MotifExport">Exporter en texte</button>
|
||||||
<button type="button" class="bouton bouton-discret" disabled
|
<button type="button" class="bouton bouton-discret" disabled
|
||||||
@@ -277,15 +280,6 @@ else
|
|||||||
{
|
{
|
||||||
<a class="bouton bouton-discret" href="souhaits/ordre">Modifier l'ordre</a>
|
<a class="bouton bouton-discret" href="souhaits/ordre">Modifier l'ordre</a>
|
||||||
}
|
}
|
||||||
@if (Reseau.EnLigne)
|
|
||||||
{
|
|
||||||
<a class="bouton bouton-principal" href="souhaits/ajout">Ajouter une envie</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="bouton bouton-principal" disabled
|
|
||||||
title="@EtatReseau.MotifHorsLigne">Ajouter une envie</button>
|
|
||||||
}
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -164,6 +164,11 @@ body {
|
|||||||
|
|
||||||
.entete-fiche-actions {
|
.entete-fiche-actions {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
/* Sans elle, un flex-basis « auto » prend la largeur de TOUS les boutons sur une seule
|
||||||
|
ligne (leur taille de contenu), et le flex-wrap ci-dessous ne peut jamais s'en servir :
|
||||||
|
la ligne déborde au lieu de se replier. Quatre boutons (dont « Modifier en lot ») y
|
||||||
|
tiennent tout juste à 1280px, mais débordent à 375px sans ce plafond. */
|
||||||
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -299,6 +304,8 @@ body {
|
|||||||
|
|
||||||
.segment.type-roman.segment-actif { background: #2a6f97; border-color: #2a6f97; }
|
.segment.type-roman.segment-actif { background: #2a6f97; border-color: #2a6f97; }
|
||||||
.segment.type-bd.segment-actif { background: #a13d63; border-color: #a13d63; }
|
.segment.type-bd.segment-actif { background: #a13d63; border-color: #a13d63; }
|
||||||
|
.segment.type-pratique.segment-actif { background: #2d6a4f; border-color: #2d6a4f; }
|
||||||
|
.segment.type-connaissance.segment-actif { background: #d97706; border-color: #d97706; }
|
||||||
|
|
||||||
/* --- Boutons --- */
|
/* --- Boutons --- */
|
||||||
|
|
||||||
@@ -823,6 +830,8 @@ body {
|
|||||||
|
|
||||||
.etiquette-type.type-roman { background: #2a6f97; }
|
.etiquette-type.type-roman { background: #2a6f97; }
|
||||||
.etiquette-type.type-bd { background: #a13d63; }
|
.etiquette-type.type-bd { background: #a13d63; }
|
||||||
|
.etiquette-type.type-pratique { background: #2d6a4f; }
|
||||||
|
.etiquette-type.type-connaissance { background: #d97706; }
|
||||||
|
|
||||||
/* --- Fiche du livre en lecture seule --- */
|
/* --- Fiche du livre en lecture seule --- */
|
||||||
|
|
||||||
@@ -1175,41 +1184,6 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Mise à jour de l'application --- */
|
|
||||||
|
|
||||||
/* Injecté par js/mise-a-jour.js, hors composants Blazor : ce bandeau doit pouvoir s'afficher
|
|
||||||
même si l'application WebAssembly n'a pas démarré. D'où une règle globale et non scopée.
|
|
||||||
Il existe parce que les empreintes des assets WASM sont désactivées (CLAUDE.md) : le service
|
|
||||||
worker est le seul cache-busting du projet, et sa mise à jour attendrait sinon la fermeture
|
|
||||||
de tous les onglets. */
|
|
||||||
#mb-maj {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 1100;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0.7rem 1rem;
|
|
||||||
background: #1b3a5c;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mb-maj button {
|
|
||||||
min-height: 2.5rem;
|
|
||||||
padding: 0.4rem 0.9rem;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #f5d76e;
|
|
||||||
color: #4a3800;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Un ISBN se relit chiffre à chiffre : une chasse fixe évite de confondre 1 et l,
|
/* Un ISBN se relit chiffre à chiffre : une chasse fixe évite de confondre 1 et l,
|
||||||
0 et O, et rend les tranches séparées par des tirets alignées d'une ligne à l'autre. */
|
0 et O, et rend les tranches séparées par des tirets alignées d'une ligne à l'autre. */
|
||||||
/* ⚠️ PAS de « white-space: nowrap » ici, et ce n'est pas un oubli. Un ISBN à tirets mesure
|
/* ⚠️ PAS de « white-space: nowrap » ici, et ce n'est pas un oubli. Un ISBN à tirets mesure
|
||||||
@@ -1258,7 +1232,11 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
flex: 0 0 auto;
|
/* flex-shrink à 0 empêchait ce bloc de rétrécir : sur un écran étroit, il gardait la
|
||||||
|
largeur de ses boutons mis bout à bout et débordait à droite au lieu de les enrouler
|
||||||
|
sur une seconde ligne, malgré le flex-wrap ci-dessus. */
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Liste d'envies : ordre choisi --- */
|
/* --- Liste d'envies : ordre choisi --- */
|
||||||
|
|||||||
@@ -43,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="_framework/blazor.webassembly.js"></script>
|
<script src="_framework/blazor.webassembly.js"></script>
|
||||||
<!-- Enregistrement du service worker déporté : il vérifie le support avant d'appeler
|
<!-- Enregistrement du service worker déporté : il vérifie le support avant d'appeler
|
||||||
navigator.serviceWorker (absent hors contexte sécurisé), journalise un échec au lieu de
|
navigator.serviceWorker (absent hors contexte sécurisé), et journalise un échec au lieu
|
||||||
l'avaler, et propose la mise à jour quand une nouvelle version est prête. -->
|
de l'avaler. Comportement de mise à jour par défaut de Blazor : voir js/mise-a-jour.js. -->
|
||||||
<script src="js/mise-a-jour.js"></script>
|
<script src="js/mise-a-jour.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Échappatoire manuelle au comportement par défaut du service worker (qui attend la fermeture
|
||||||
|
// de TOUS les onglets avant d'activer une nouvelle version — voir js/mise-a-jour.js) : on
|
||||||
|
// désinscrit le worker et on vide le Cache Storage qu'il a rempli, puis on recharge. La page
|
||||||
|
// suivante repart de zéro et réenregistre un worker propre sur les fichiers actuels du serveur.
|
||||||
|
//
|
||||||
|
// ⚠️ Ne touche PAS à IndexedDB (cache-hors-ligne.js) : ces instantanés sont les données de
|
||||||
|
// lecture hors-ligne de l'utilisateur, pas du code périmé — les effacer n'aiderait à rien ici
|
||||||
|
// et ferait perdre la seule consultation possible sans réseau.
|
||||||
|
export async function forcerMiseAJour() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
const enregistrements = await navigator.serviceWorker.getRegistrations();
|
||||||
|
await Promise.all(enregistrements.map(enregistrement => enregistrement.unregister()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('caches' in self) {
|
||||||
|
const cles = await caches.keys();
|
||||||
|
await Promise.all(cles.map(cle => caches.delete(cle)));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
// Enregistrement du service worker, application automatique au démarrage, bandeau en secours.
|
// Enregistrement du service worker — comportement par défaut de Blazor : un nouveau worker
|
||||||
|
// attend que tous les onglets de l'application soient fermés avant de prendre la main. Pas de
|
||||||
|
// SKIP_WAITING, pas de rechargement forcé, pas de bandeau : la mise à jour a lieu au prochain
|
||||||
|
// lancement de l'application, silencieusement.
|
||||||
//
|
//
|
||||||
// Pourquoi ce fichier existe (et pourquoi il n'est pas qu'une ligne `register(...)`) :
|
// ⚠️ Revenu en arrière le 2026-09-10 : le mécanisme précédent (application automatique dans les
|
||||||
|
// 10 premières secondes, sinon bandeau « Mettre à jour ») ne se montrait pas fiable en pratique
|
||||||
|
// (constaté sur l'application installée, PC comme téléphone : la version affichée dans « À
|
||||||
|
// propos » — qui vient d'un appel au serveur, donc toujours à jour — changeait sans que le reste
|
||||||
|
// de l'application suive). Le comportement par défaut est plus simple à raisonner ; s'il rebloque
|
||||||
|
// comme en août 2026, le correctif portera sur un diagnostic visible avant de complexifier à
|
||||||
|
// nouveau ce fichier.
|
||||||
//
|
//
|
||||||
// 1. Les empreintes des assets WASM sont DÉSACTIVÉES (voir CLAUDE.md). `blazor.webassembly.js`
|
// Ce qui reste, et pourquoi :
|
||||||
// et `dotnet.js` portent donc des noms stables, et c'est le service worker — lui seul — qui
|
// 1. `navigator.serviceWorker` n'existe pas en contexte non sécurisé (http sur une IP locale).
|
||||||
// empêche de servir éternellement une version périmée. Le mécanisme de Blazor fonctionne,
|
|
||||||
// mais il est silencieux et différé : le nouveau worker attend que TOUS les onglets de
|
|
||||||
// l'application soient fermés. Sur mobile, un onglet oublié fige la mise à jour sans que
|
|
||||||
// personne comprenne pourquoi.
|
|
||||||
//
|
|
||||||
// 2. `navigator.serviceWorker` n'existe pas en contexte non sécurisé (http sur une IP locale).
|
|
||||||
// L'appeler sans vérification lève une TypeError qui casse le script — et fait croire à un
|
// L'appeler sans vérification lève une TypeError qui casse le script — et fait croire à un
|
||||||
// défaut de la PWA alors que c'est le contexte qui n'est pas éligible.
|
// défaut de la PWA alors que c'est le contexte qui n'est pas éligible.
|
||||||
//
|
// 2. Chemin ABSOLU, et scope explicite. `register('service-worker.js')` se résout contre l'URL
|
||||||
// ⚠️ Le clic n'est plus obligatoire (décidé le 2026-08-20, après un blocage constaté sur
|
// du DOCUMENT et non contre <base href="/"> : ouvrir l'application sur une route profonde
|
||||||
// l'application installée sur téléphone). CLAUDE.md actait « jamais tout seul » pour ne pas
|
// (/souhaits, /livres/3) visait /souhaits/service-worker.js, que le serveur ne sert pas —
|
||||||
// recharger sous une saisie en cours ; la raison reste valable, mais elle ne vaut que pour une
|
// l'enregistrement échouait alors silencieusement, et avec lui tout le hors-ligne.
|
||||||
// session déjà entamée. Voir FENETRE_DEMARRAGE ci-dessous.
|
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -28,121 +30,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fenêtre pendant laquelle une nouvelle version s'applique TOUTE SEULE, sans rien demander.
|
// updateViaCache: 'none' — sans lien avec le comportement d'application (ci-dessus) : ceci
|
||||||
// Au-delà, l'utilisateur est en train de faire quelque chose (une fiche à moitié saisie, un
|
// évite seulement qu'un cache HTTP intermédiaire masque une nouvelle version du script lui-
|
||||||
// prêt en cours d'enregistrement) : on repasse par le bandeau, pour ne pas lui arracher son
|
// même, y compris service-worker-assets.js qu'il importe.
|
||||||
// travail. Ouvrir l'application depuis l'écran d'accueil du téléphone tombe toujours dans
|
|
||||||
// cette fenêtre — c'est exactement le cas qui ne se mettait plus à jour.
|
|
||||||
var FENETRE_DEMARRAGE = 10000;
|
|
||||||
|
|
||||||
// Si la reprise en main n'a pas lieu, on recharge quand même : le pire scénario est de
|
|
||||||
// laisser l'utilisateur devant un bouton grisé qui ne fait rien.
|
|
||||||
var DELAI_REPRISE = 5000;
|
|
||||||
|
|
||||||
// Une seule reprise forcée par session : si la nouvelle version n'arrive pas à s'activer,
|
|
||||||
// recharger en boucle transformerait une mise à jour ratée en application inutilisable.
|
|
||||||
var CLE_FORCE = 'mb-maj-forcee';
|
|
||||||
|
|
||||||
var demandee = false;
|
|
||||||
var recharge = false;
|
|
||||||
|
|
||||||
function recharger() {
|
|
||||||
if (recharge) return;
|
|
||||||
recharge = true;
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function appliquer(enAttente) {
|
|
||||||
demandee = true;
|
|
||||||
// Le worker en attente prend la main sans qu'on ait à fermer tous les onglets.
|
|
||||||
enAttente.postMessage({ type: 'SKIP_WAITING' });
|
|
||||||
|
|
||||||
// Filet. `controllerchange` devrait suivre ; s'il ne vient pas (worker en attente issu
|
|
||||||
// d'une version antérieure au gestionnaire SKIP_WAITING, reprise en main qui échoue),
|
|
||||||
// rien ne se passerait du tout et le bouton resterait grisé pour toujours.
|
|
||||||
window.setTimeout(function () {
|
|
||||||
if (recharge) return;
|
|
||||||
var dejaForcee = false;
|
|
||||||
try {
|
|
||||||
dejaForcee = window.sessionStorage.getItem(CLE_FORCE) === '1';
|
|
||||||
window.sessionStorage.setItem(CLE_FORCE, '1');
|
|
||||||
} catch (e) {
|
|
||||||
// Navigation privée ou stockage refusé : on préfère ne pas forcer plutôt que
|
|
||||||
// risquer une boucle de rechargement qu'on ne saurait plus arrêter.
|
|
||||||
dejaForcee = true;
|
|
||||||
}
|
|
||||||
if (!dejaForcee) recharger();
|
|
||||||
}, DELAI_REPRISE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function proposerLaMiseAJour(enAttente) {
|
|
||||||
if (document.getElementById('mb-maj')) return;
|
|
||||||
|
|
||||||
var barre = document.createElement('div');
|
|
||||||
barre.id = 'mb-maj';
|
|
||||||
barre.setAttribute('role', 'status');
|
|
||||||
barre.textContent = 'Une nouvelle version de MaBibli est disponible. ';
|
|
||||||
|
|
||||||
var bouton = document.createElement('button');
|
|
||||||
bouton.type = 'button';
|
|
||||||
bouton.textContent = 'Mettre à jour';
|
|
||||||
bouton.addEventListener('click', function () {
|
|
||||||
bouton.disabled = true;
|
|
||||||
bouton.textContent = 'Mise à jour…';
|
|
||||||
appliquer(enAttente);
|
|
||||||
});
|
|
||||||
|
|
||||||
barre.appendChild(bouton);
|
|
||||||
document.body.appendChild(barre);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Au démarrage : on applique sans demander. Plus tard : on propose.
|
|
||||||
function traiter(enAttente) {
|
|
||||||
if (performance.now() < FENETRE_DEMARRAGE) {
|
|
||||||
appliquer(enAttente);
|
|
||||||
} else {
|
|
||||||
proposerLaMiseAJour(enAttente);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ⚠️ Chemin ABSOLU, et scope explicite. `register('service-worker.js')` se résout contre
|
|
||||||
// l'URL du DOCUMENT et non contre <base href="/"> : ouvrir l'application sur une route
|
|
||||||
// profonde (/souhaits, /livres/3) visait /souhaits/service-worker.js, que le serveur ne sert
|
|
||||||
// pas — l'enregistrement échouait alors silencieusement, et avec lui tout le hors-ligne.
|
|
||||||
navigator.serviceWorker.register('/service-worker.js', { scope: '/', updateViaCache: 'none' })
|
navigator.serviceWorker.register('/service-worker.js', { scope: '/', updateViaCache: 'none' })
|
||||||
.then(function (enregistrement) {
|
|
||||||
// Un worker déjà installé attendait peut-être depuis la visite précédente.
|
|
||||||
if (enregistrement.waiting && navigator.serviceWorker.controller) {
|
|
||||||
traiter(enregistrement.waiting);
|
|
||||||
}
|
|
||||||
|
|
||||||
enregistrement.addEventListener('updatefound', function () {
|
|
||||||
var nouveau = enregistrement.installing;
|
|
||||||
if (!nouveau) return;
|
|
||||||
|
|
||||||
nouveau.addEventListener('statechange', function () {
|
|
||||||
// `controller` non nul = ce n'est pas la première installation, donc il y a
|
|
||||||
// bien une version précédente à remplacer.
|
|
||||||
if (nouveau.state === 'installed' && navigator.serviceWorker.controller) {
|
|
||||||
traiter(nouveau);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Vérification explicite à chaque chargement : ne pas dépendre du seul rythme
|
|
||||||
// interne du navigateur pour découvrir une version plus récente.
|
|
||||||
enregistrement.update().catch(function () { /* hors-ligne : sans objet */ });
|
|
||||||
})
|
|
||||||
.catch(function (erreur) {
|
.catch(function (erreur) {
|
||||||
// Ne jamais laisser cet échec passer inaperçu : sans service worker, l'application
|
// Ne jamais laisser cet échec passer inaperçu : sans service worker, l'application
|
||||||
// ne démarre pas hors-ligne, et le cache IndexedDB ne sert alors à rien.
|
// ne démarre pas hors-ligne, et le cache IndexedDB ne sert alors à rien.
|
||||||
console.error("Échec de l'enregistrement du service worker :", erreur);
|
console.error("Échec de l'enregistrement du service worker :", erreur);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Le rechargement n'a lieu que si la mise à jour a été demandée — automatiquement ou par un
|
|
||||||
// clic. Un changement de contrôleur survient aussi à la toute première installation, et
|
|
||||||
// recharger la page à ce moment-là serait un clignotement inexplicable.
|
|
||||||
navigator.serviceWorker.addEventListener('controllerchange', function () {
|
|
||||||
if (demandee) recharger();
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ self.addEventListener('install', event => event.waitUntil(onInstall(event)));
|
|||||||
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
|
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
|
||||||
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||||
|
|
||||||
// Mise à jour à la demande. Sans cela, un nouveau worker attend que TOUS les onglets de
|
// Comportement par défaut : pas de SKIP_WAITING. Un nouveau worker attend que TOUS les onglets
|
||||||
// l'application soient fermés — un onglet oublié fige indéfiniment l'utilisateur sur l'ancienne
|
// de l'application soient fermés avant de prendre la main — la mise à jour a lieu au prochain
|
||||||
// version. C'est d'autant plus important ici que les empreintes des assets WASM sont désactivées
|
// lancement, jamais au milieu d'une session. Revenu en arrière le 2026-09-10 après un mécanisme
|
||||||
// (voir CLAUDE.md) : ce worker est le SEUL mécanisme de cache-busting du projet.
|
// de mise à jour immédiate qui ne se montrait pas fiable en pratique (voir js/mise-a-jour.js).
|
||||||
// Le message ne vient que de js/mise-a-jour.js, après un clic explicite : jamais tout seul, pour
|
|
||||||
// ne pas mélanger deux versions au milieu d'une session.
|
|
||||||
self.addEventListener('message', event => {
|
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
|
|
||||||
});
|
|
||||||
|
|
||||||
const cacheNamePrefix = 'offline-cache-';
|
const cacheNamePrefix = 'offline-cache-';
|
||||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||||
@@ -46,10 +41,9 @@ async function onActivate(event) {
|
|||||||
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
|
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
|
||||||
.map(key => caches.delete(key)));
|
.map(key => caches.delete(key)));
|
||||||
|
|
||||||
// Prendre la main sur les pages déjà ouvertes. skipWaiting() est censé le faire seul, mais
|
// Sans effet sur les onglets déjà ouverts avant cette activation (ils ont fermé pour qu'elle
|
||||||
// c'est ce qui déclenche `controllerchange`, donc le rechargement côté page : sans reprise
|
// ait lieu) ; utile seulement pour la toute première installation, où aucun worker ne
|
||||||
// effective, le bouton « Mettre à jour » se grise et il ne se passe plus rien — symptôme
|
// contrôlait encore la page.
|
||||||
// observé sur téléphone. Cet appel est sans effet quand la reprise a déjà eu lieu.
|
|
||||||
await self.clients.claim();
|
await self.clients.claim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using MaBibli.Shared.Entites;
|
||||||
|
|
||||||
namespace MaBibli.Shared.Dtos;
|
namespace MaBibli.Shared.Dtos;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -27,6 +29,9 @@ public record SerieDto
|
|||||||
/// <summary>Qui a créé la série. Trace d'affichage : la série est commune au foyer.</summary>
|
/// <summary>Qui a créé la série. Trace d'affichage : la série est commune au foyer.</summary>
|
||||||
public string? AjoutePar { get; init; }
|
public string? AjoutePar { get; init; }
|
||||||
|
|
||||||
|
/// <summary>État de progression de la série (en cours, terminée, abandonnée).</summary>
|
||||||
|
public EtatSerie Etat { get; init; } = EtatSerie.EnCours;
|
||||||
|
|
||||||
/// <summary>Tomes possédés sur tomes recensés — « 4 sur 7 », le cœur de l'écran.</summary>
|
/// <summary>Tomes possédés sur tomes recensés — « 4 sur 7 », le cœur de l'écran.</summary>
|
||||||
public int NombrePossedes => Elements.Count(e => e.Possede);
|
public int NombrePossedes => Elements.Count(e => e.Possede);
|
||||||
}
|
}
|
||||||
@@ -68,6 +73,9 @@ public record EnregistrementSerie
|
|||||||
|
|
||||||
/// <summary>Série parente, pour ranger une trilogie dans un cycle. <c>null</c> = à la racine.</summary>
|
/// <summary>Série parente, pour ranger une trilogie dans un cycle. <c>null</c> = à la racine.</summary>
|
||||||
public int? SerieParenteId { get; set; }
|
public int? SerieParenteId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>État de progression de la série (en cours, terminée, abandonnée).</summary>
|
||||||
|
public EtatSerie Etat { get; set; } = EtatSerie.EnCours;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MaBibli.Shared.Entites;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// État de progression d'une série.
|
||||||
|
/// </summary>
|
||||||
|
public enum EtatSerie
|
||||||
|
{
|
||||||
|
EnCours = 0,
|
||||||
|
Terminee = 1,
|
||||||
|
Abandonnee = 2,
|
||||||
|
}
|
||||||
@@ -61,6 +61,9 @@ public class Serie
|
|||||||
/// <summary>Qui a créé la série. <b>Trace, pas frontière</b> : la série est commune.</summary>
|
/// <summary>Qui a créé la série. <b>Trace, pas frontière</b> : la série est commune.</summary>
|
||||||
public string? AjoutePar { get; set; }
|
public string? AjoutePar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>État de progression de la série (en cours, terminée, abandonnée).</summary>
|
||||||
|
public EtatSerie Etat { get; set; } = EtatSerie.EnCours;
|
||||||
|
|
||||||
public void RecalculerFormes()
|
public void RecalculerFormes()
|
||||||
{
|
{
|
||||||
Titre = Titre.Trim();
|
Titre = Titre.Trim();
|
||||||
|
|||||||
@@ -31,4 +31,8 @@ public enum TypeDocument
|
|||||||
Roman = 1,
|
Roman = 1,
|
||||||
|
|
||||||
BandeDessinee = 2,
|
BandeDessinee = 2,
|
||||||
|
|
||||||
|
Pratique = 3,
|
||||||
|
|
||||||
|
Connaissance = 4,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user