Cinq retours d'IDEES.md, tous pensés pour le mobile d'abord.
Fiche du livre en deux modes, portés par deux routes : « /livres/{id} »
consulte, « /livres/{id}/edition » modifie. Ouvrir une fiche depuis le
catalogue ne place plus un formulaire vivant sous le pouce. Mettre le mode
dans l'adresse fait du retour navigateur une sortie d'édition naturelle.
Piège rencontré : les deux routes visent le même composant avec le même Id,
donc aucun paramètre ne change et le routeur ne redessine pas — l'URL passait
à « /edition » sans que l'écran bascule, alors qu'un chargement direct de
cette URL marchait, ce qui masque le défaut. Le composant s'abonne donc à
LocationChanged.
Restent hors « Éditer », parce que ce ne sont pas des modifications de la
fiche : le statut de lecture (action la plus fréquente, et personnelle) et
les prêts (gestes du quotidien, donnée du foyer). La suppression, elle, est
réservée au mode édition.
Dans la liste, le statut devient une pastille en lecture seule sur la ligne
des étiquettes ; les trois boutons actionnables par carte partaient trop
facilement en faisant défiler. Le format ne s'affiche plus que pour les
ebooks, le physique étant le cas par défaut — le formulaire le
présélectionne déjà, l'énumération valant 0.
Couverture agrandissable au clic, fermeture au clic extérieur ou par Échap
(le calque est focalisé à l'ouverture, sans quoi Échap resterait sans effet).
Le substitut à initiale n'est jamais cliquable : le déclencheur n'existe que
s'il y a une image.
Le regroupement par auteur existait déjà (table Auteur, page /auteurs, filtre
« ?auteur= ») ; y manquaient les liens depuis la fiche et les styles de la
page auteurs, jamais écrits — la liste sortait en puces brutes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
4.7 KiB
Plaintext
121 lines
4.7 KiB
Plaintext
@*
|
|
Formulaire d'une fiche livre, partagé par l'ajout (ISBN ou manuel) et l'édition.
|
|
|
|
Le pré-remplissage par ISBN est une AIDE, pas une contrainte : tous les champs restent
|
|
modifiables, et une fiche incomplète peut toujours être complétée à la main (CLAUDE.md).
|
|
*@
|
|
<div class="formulaire">
|
|
@if (!string.IsNullOrEmpty(Saisie.CoverUrl) || !string.IsNullOrWhiteSpace(Saisie.Titre))
|
|
{
|
|
<div class="formulaire-apercu">
|
|
<Couverture Url="@Saisie.CoverUrl" Titre="@Saisie.Titre" Classe="couverture-moyenne" />
|
|
</div>
|
|
}
|
|
|
|
<label class="champ">
|
|
<span class="champ-libelle">Titre <span class="obligatoire" aria-hidden="true">*</span></span>
|
|
<input class="champ-saisie" type="text" @bind="Saisie.Titre" @bind:event="oninput"
|
|
placeholder="Titre du livre" autocomplete="off" />
|
|
</label>
|
|
|
|
@*
|
|
Un seul champ, plusieurs auteurs : le point-virgule les sépare. La virgule est déjà
|
|
prise par la forme bibliographique « Zola, Émile », qu'on doit pouvoir taper telle quelle.
|
|
Le serveur rapproche ensuite les variantes sûres d'un auteur déjà connu.
|
|
*@
|
|
<label class="champ">
|
|
<span class="champ-libelle">
|
|
Auteur<span class="champ-aide"> (plusieurs : séparez par « ; »)</span>
|
|
</span>
|
|
<input class="champ-saisie" type="text" @bind="Saisie.Auteur" @bind:event="oninput"
|
|
placeholder="Prénom Nom ; Prénom Nom" autocomplete="off" />
|
|
</label>
|
|
|
|
<label class="champ">
|
|
<span class="champ-libelle">Éditeur</span>
|
|
<input class="champ-saisie" type="text" @bind="Saisie.Editeur" @bind:event="oninput"
|
|
autocomplete="off" />
|
|
</label>
|
|
|
|
<label class="champ">
|
|
<span class="champ-libelle">ISBN <span class="champ-aide">(facultatif)</span></span>
|
|
<input class="champ-saisie" type="text" inputmode="numeric" @bind="Saisie.Isbn" @bind:event="oninput"
|
|
placeholder="978…" autocomplete="off" />
|
|
</label>
|
|
|
|
@*
|
|
Le format n'est plus affiché dans les listes que pour les ebooks : un livre y est donc
|
|
physique par défaut. Cette valeur DOIT rester présélectionnée ici, sinon on créerait
|
|
des fiches sans format en croyant que « rien d'affiché = physique ».
|
|
C'est le cas sans code particulier : Format.Physique vaut 0, valeur par défaut de
|
|
l'énumération, et le segment correspondant est donc actif dès l'ouverture.
|
|
*@
|
|
<fieldset class="champ">
|
|
<legend class="champ-libelle">
|
|
Format<span class="champ-aide"> (physique par défaut)</span>
|
|
</legend>
|
|
<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>
|
|
|
|
<fieldset class="champ">
|
|
<legend class="champ-libelle">
|
|
Statut de lecture<span class="champ-aide"> (le vôtre)</span>
|
|
</legend>
|
|
<ChoixStatut Valeur="Saisie.Statut" ValeurChanged="s => Saisie.Statut = s" />
|
|
</fieldset>
|
|
|
|
<label class="champ">
|
|
<span class="champ-libelle">URL de couverture</span>
|
|
<input class="champ-saisie" type="url" @bind="Saisie.CoverUrl" @bind:event="oninput"
|
|
placeholder="https://…" autocomplete="off" />
|
|
</label>
|
|
|
|
@if (!string.IsNullOrEmpty(Erreur))
|
|
{
|
|
<p class="message-erreur" role="alert">@Erreur</p>
|
|
}
|
|
|
|
<div class="actions-formulaire">
|
|
<button type="button" class="bouton bouton-principal"
|
|
disabled="@(EnCours || string.IsNullOrWhiteSpace(Saisie.Titre))"
|
|
@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>();
|
|
|
|
[Parameter, EditorRequired] public EnregistrementLivre Saisie { get; set; } = new();
|
|
|
|
[Parameter] public string LibelleValidation { get; set; } = "Enregistrer";
|
|
|
|
[Parameter] public string? Erreur { get; set; }
|
|
|
|
[Parameter] public bool EnCours { get; set; }
|
|
|
|
[Parameter] public EventCallback OnValider { get; set; }
|
|
|
|
[Parameter] public EventCallback OnAnnuler { get; set; }
|
|
|
|
private Task Valider() => OnValider.InvokeAsync();
|
|
}
|