From 8223860efb0d1abe2e97041d494d1eed92e7bcf7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 19 Sep 2026 07:39:51 +0000 Subject: [PATCH] =?UTF-8?q?Le=20num=C3=A9ro=20d'un=20b=C3=A9n=C3=A9vole=20?= =?UTF-8?q?se=20distingue,=20et=20se=20lit=20=C3=A0=20voix=20haute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le pli et le gris ont été corrigés au commit précédent : les inscrits s'affichent sous leur créneau, coordonnées cliquables à 7,24:1. Restaient quatre choses, vérifiées sur l'écran tel qu'il est aujourd'hui. Le téléphone ne se distinguait pas de l'adresse. Les deux sortaient en text-xs slate-600, l'un sous l'autre, sans rien pour dire lequel on vient chercher. Le numéro passe devant, en text-sm brand-700 (6,78:1), avec son icône ; l'adresse reste en text-xs slate-600 (7,24:1), avec la sienne. Les liens n'avaient pas de nom. Un lecteur d'écran annonçait « lien, +33634580545 » sans dire à qui. Le nom est préfixé en sr-only plutôt que posé en aria-label : un aria-label aurait REMPLACÉ le nom accessible, donc retiré le numéro visible de ce qui est annoncé, et cassé la commande vocale — c'est le critère 2.5.3 « Label in Name ». Le motif est celui que le bouton « Retirer » utilisait déjà. Trois cibles restaient sous 44 px, alors que le commit précédent annonçait le contraire : « Retirer » n'avait aucune hauteur minimale (~20 px), et Modifier / Dupliquer / Supprimer étaient à min-h-8. Le débordement n'était traité qu'à moitié. L'adresse portait `break-all`, qui coupe n'importe où — y compris un numéro entre deux chiffres — et le téléphone ne portait rien du tout, alors qu'il accepte 40 caractères sans séparateur. Les deux passent à `[overflow-wrap:anywhere]`, qui ne coupe qu'en dernier recours et qui, contrairement à `break-words`, entre dans le calcul de min-content : c'est ce qui empêche la carte de sortir de l'écran à 320 px. Le nom le reçoit aussi. Déplier coûte de la hauteur — douze inscrits font environ 1 300 px sur une colonne. Elle est reprise en largeur, deux colonnes dès 640 px et trois dès 1280 px, plutôt qu'en repliant à nouveau ce qu'on vient de montrer. Deux échecs silencieux au passage : le bloc d'erreur n'était rendu que si le formulaire d'ajout était ouvert, alors que la duplication, la suppression d'un créneau et le retrait d'un inscrit alimentent le même `error` — une duplication qui échouait ne disait rien. Il sort du formulaire, et les deux suppressions, qui n'attrapaient même pas leur exception, la signalent. Côté réunion, le même traitement : icône, taille et couleur identiques, nom accessible, et le badge « invité » quitte slate-100/slate-500 (4,34:1, sous le seuil) pour slate-200/slate-600 (6,15:1). Les deux écrans montrent la même chose ; ils la montrent enfin de la même façon. Enfin la dernière lecture publique qui ramenait trop : getUpcomingPublishedEvents chargeait la ligne d'inscription entière — nom, e-mail, téléphone, jeton d'annulation — pour l'accueil et /rejoindre, qui n'en lisent que `.length`. Elle ne charge plus que l'identifiant. Aucun effet visible, et la fuite devient impossible par construction plutôt que par discipline. Ce qui n'est PAS fait, volontairement : les événements lisent toujours `signup.phone` brut, sans le repli vers la fiche d'adhérent que les réunions obtiennent par telephonePresent(). Un membre connecté qui s'inscrit sans retaper son numéro apparaît donc sans téléphone. Le corriger demande de joindre associationMember dans getEventWithDetails, qui a cinq appelants — dont l'outil MCP get_event, qui relaie l'objet entier et sortirait le numéro privé d'un adhérent. C'est un autre changement, avec sa propre revue. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014SfQYBU4xXTeSEHKhHQXdD --- src/app/dashboard/events/[id]/page.tsx | 24 +++++- src/components/slot-manager.tsx | 104 +++++++++++++++++++------ src/lib/data.ts | 10 ++- 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/src/app/dashboard/events/[id]/page.tsx b/src/app/dashboard/events/[id]/page.tsx index b5fbb3c..ceddc15 100644 --- a/src/app/dashboard/events/[id]/page.tsx +++ b/src/app/dashboard/events/[id]/page.tsx @@ -18,6 +18,7 @@ import { MapPin, Paperclip, Pencil, + Phone, Ticket, UserRoundPlus, UsersRound, @@ -297,19 +298,34 @@ export default async function EventDetailPage({ l'annuaire des membres, et rappelle que la réunion est ouverte. */} {!r.userId && ( - + invité )} {/* Cliquable : celui qui consulte cette liste pour relancer quelqu'un est le plus souvent sur son - téléphone, la veille de la réunion. */} + téléphone, la veille de la réunion. + Même écriture que les inscrits d'un événement — + icône, taille et couleur : les deux écrans + montrent la même chose, ils doivent la montrer de + la même façon. */} {telephone && ( - {telephone} + )} diff --git a/src/components/slot-manager.tsx b/src/components/slot-manager.tsx index 4a73484..1044df2 100644 --- a/src/components/slot-manager.tsx +++ b/src/components/slot-manager.tsx @@ -1,5 +1,6 @@ "use client"; +import { Mail, Phone } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -128,14 +129,27 @@ export function SlotManager({ async function deleteSlot(id: string) { if (!confirm("Supprimer ce créneau et ses inscriptions ?")) return; - await api(`/api/slots/${id}`, { method: "DELETE" }); - router.refresh(); + setBusySlotId(id); + setError(null); + try { + await api(`/api/slots/${id}`, { method: "DELETE" }); + router.refresh(); + } catch (err) { + setError((err as Error).message); + } finally { + setBusySlotId(null); + } } async function removeSignup(id: string) { if (!confirm("Retirer cette inscription ?")) return; - await api(`/api/signups/${id}`, { method: "DELETE" }); - router.refresh(); + setError(null); + try { + await api(`/api/signups/${id}`, { method: "DELETE" }); + router.refresh(); + } catch (err) { + setError((err as Error).message); + } } return ( @@ -154,16 +168,21 @@ export function SlotManager({ )} + {/* Hors du formulaire d'ajout : il n'était rendu que si celui-ci était + ouvert, alors que la duplication, l'enregistrement, la suppression + d'un créneau et le retrait d'un inscrit alimentent le même `error`. + Une duplication qui échouait ne disait donc rien du tout. */} + {error && ( +

+ {error} +

+ )} + {canManage && showAdd && (
- {error && ( -

- {error} -

- )}
{editingId === slot.id ? "Fermer" : "Modifier"} @@ -278,14 +297,14 @@ export function SlotManager({ type="button" onClick={() => duplicateSlot(slot)} disabled={busySlotId === slot.id} - className="min-h-8 rounded px-1 text-xs font-semibold text-slate-600 transition-colors hover:text-brand-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 disabled:opacity-50" + className="min-h-11 rounded px-2 text-xs font-semibold text-slate-600 transition-colors hover:text-brand-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 disabled:opacity-50" > {busySlotId === slot.id ? "Copie…" : "Dupliquer"} @@ -400,48 +419,83 @@ export function SlotManager({ téléphone, pour joindre quelqu'un. */} {slot.signups.length > 0 && (
-

+

{slot.signups.length} personne {slot.signups.length > 1 ? "s" : ""} inscrite {slot.signups.length > 1 ? "s" : ""}

-
{canManage && (