From 0a3b8523efec98786b5be50f87f90943ad602d88 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Wed, 6 May 2026 23:34:33 +0200 Subject: [PATCH] =?UTF-8?q?feat(plans/wizard):=20=C3=A9diteur=20avec=20ic?= =?UTF-8?q?=C3=B4nes=20de=20tonalit=C3=A9=20+=20toggle=20de=20s=C3=A9lecti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Champ Décalage retiré : on change le timing en cliquant une autre case du calendrier (delete + click ailleurs), c'est plus aligné avec la métaphore calendrier - Tonalité passée d'un select à un groupe de 4 boutons icônes : · Doux → Smile (sourire chaleureux) · Standard → MessageCircle (bulle de conversation polie) · Ferme → AlertTriangle (alerte mesurée) · Strict → Gavel (marteau de juge) Chaque bouton actif prend la couleur de fond de sa tonalité, plus visuel et compact qu'un dropdown - Header de l'éditeur : la pastille colorée devient une pastille avec l'icône de tonalité dedans → on lit la tonalité d'un coup d'œil - Toggle : re-cliquer la case déjà sélectionnée la désélectionne (retour à l'état "vue d'ensemble" avec le hint), au lieu d'avoir une sélection permanente Co-Authored-By: Claude Opus 4.7 --- .../plans/wizard/CadenceCalendar.tsx | 166 ++++++++---------- apps/web/src/routes/_app/plans_.nouveau.tsx | 6 +- 2 files changed, 82 insertions(+), 90 deletions(-) diff --git a/apps/web/src/components/plans/wizard/CadenceCalendar.tsx b/apps/web/src/components/plans/wizard/CadenceCalendar.tsx index c47bdd3..2c329f5 100644 --- a/apps/web/src/components/plans/wizard/CadenceCalendar.tsx +++ b/apps/web/src/components/plans/wizard/CadenceCalendar.tsx @@ -1,11 +1,32 @@ import { useEffect, useMemo, useState } from "react"; -import { ChevronLeft, ChevronRight, Plus, Trash2, AlertTriangle } from "lucide-react"; +import { + ChevronLeft, + ChevronRight, + Plus, + Trash2, + AlertTriangle, + Smile, + MessageCircle, + Gavel, + type LucideIcon, +} from "lucide-react"; import { toast } from "sonner"; import type { RelanceTone } from "@rubis/shared"; import { cn } from "@/lib/utils"; import { TONE_LABELS } from "@/lib/plans"; -import { Input } from "@/components/ui/Input"; + +/** + * Icône qui illustre chaque tonalité. Cohérent avec l'escalade émotionnelle : + * sourire pour amical, bulle de conversation pour standard, alerte pour + * ferme, marteau de juge pour mise en demeure. + */ +const TONE_ICON: Record = { + amical: Smile, + courtois: MessageCircle, + ferme: AlertTriangle, + mise_en_demeure: Gavel, +}; /** * Étape minimale pour la visu calendrier (subject/body arrivent à @@ -404,8 +425,16 @@ function DayCell({ } // ============================================================================ -// Éditeur compact — header explicite + 1 ligne de champs +// Éditeur compact — header avec icône de tonalité + sélecteur 4 icônes // ============================================================================ + +const TONE_ORDER: RelanceTone[] = [ + "amical", + "courtois", + "ferme", + "mise_en_demeure", +]; + function CompactEditor({ step, stepDate, @@ -419,18 +448,22 @@ function CompactEditor({ onUpdate: (patch: Partial) => void; onRemove: () => void; }) { + const HeaderIcon = TONE_ICON[step.tone]; + return ( -
- {/* Header explicite : on sait quelle relance on édite */} +
+ {/* Header : pastille tonalité (icône + couleur) + date + supprimer */}

Relance du{" "} @@ -455,41 +488,43 @@ function CompactEditor({ )}

-
- - + {/* Sélecteur de tonalité : 4 boutons icône, plus visuel qu'un select */} +
+

Tonalité

+
+ {TONE_ORDER.map((t) => { + const Icon = TONE_ICON[t]; + const isActive = step.tone === t; + return ( + + ); + })} +
{step.tone === "mise_en_demeure" && ( @@ -559,50 +594,3 @@ function formatShortDate(d: Date): string { return FR_SHORT.format(d); } -// ============================================================================ -// OffsetInput -// ============================================================================ -function OffsetInput({ - id, - value, - onCommit, -}: { - id: string; - value: number; - onCommit: (n: number) => void; -}) { - const [local, setLocal] = useState(String(value)); - - useEffect(() => { - setLocal(String(value)); - }, [value]); - - return ( - { - const next = e.target.value; - if (next === "" || next === "-" || /^-?\d{0,3}$/.test(next)) { - setLocal(next); - if (next !== "" && next !== "-") { - const parsed = parseInt(next, 10); - if (!Number.isNaN(parsed)) { - onCommit(Math.max(-30, Math.min(180, parsed))); - } - } - } - }} - onBlur={() => { - if (local === "" || local === "-") { - setLocal("0"); - onCommit(0); - } - }} - /> - ); -} diff --git a/apps/web/src/routes/_app/plans_.nouveau.tsx b/apps/web/src/routes/_app/plans_.nouveau.tsx index 9f83a7a..fbadab8 100644 --- a/apps/web/src/routes/_app/plans_.nouveau.tsx +++ b/apps/web/src/routes/_app/plans_.nouveau.tsx @@ -476,7 +476,11 @@ function StepCadence({ draft, onChange }: { draft: Draft; onChange: (d: Draft) = steps={draft.steps} selectedIndex={selectedIdx} globalTone={draft.globalTone} - onSelectStep={setSelectedIdx} + // Toggle : re-cliquer une étape déjà sélectionnée la désélectionne + // pour revenir à la vue d'ensemble. + onSelectStep={(idx) => + setSelectedIdx((cur) => (cur === idx ? -1 : idx)) + } onUpdateStep={updateStep} onAddStep={addStep} onAddStepAtOffset={addStepAtOffset}