From a136c5450147dc7d5d86e1a7a8be95723a31c023 Mon Sep 17 00:00:00 2001
From: ordinarthur <@arthurbarre.js@gmail.com>
Date: Wed, 6 May 2026 23:18:27 +0200
Subject: [PATCH] =?UTF-8?q?feat(plans/wizard):=20cadence=20sur=20calendrie?=
=?UTF-8?q?r=20mensuel=20avec=20tonalit=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Remplace la liste verticale par une vraie visu calendrier qui ancre
chaque étape sur une date concrète, ce qui donne du sens au timing.
- Date d'échéance fictive : le 15 du mois prochain (stable, prévisible,
laisse de la marge avant/après pour offsets négatifs comme positifs)
- Cellule échéance = ◆ rubis plein sur fond rubis-glow + shadow rubis,
jour mis en exergue
- Cellule étape = couleur de fond pleine selon la tonalité (Doux =
rubis-glow, Standard = cream-2, Ferme = ink, Strict = rubis-deep)
avec affichage J+X / numéro du jour
- Cellule jour normal = numéro muted, today souligné en rubis-glow
- Click sur cellule étape → sélection, l'éditeur (offset, ton,
supprimer) apparaît directement sous le calendrier
- Légende des tonalités juste sous l'en-tête
- Affiche tous les mois entre la 1re et la dernière étape (échéance
incluse) — typiquement 1 à 2 mois en pratique
- Mêmes raccourcis qu'avant : OffsetInput string-controlled qui accepte
les états intermédiaires "" et "-"
Suppression de CadenceTimeline.tsx (la liste verticale précédente).
Co-Authored-By: Claude Opus 4.7
---
.../plans/wizard/CadenceCalendar.tsx | 530 ++++++++++++++++++
.../plans/wizard/CadenceTimeline.tsx | 284 ----------
apps/web/src/routes/_app/plans_.nouveau.tsx | 4 +-
3 files changed, 532 insertions(+), 286 deletions(-)
create mode 100644 apps/web/src/components/plans/wizard/CadenceCalendar.tsx
delete mode 100644 apps/web/src/components/plans/wizard/CadenceTimeline.tsx
diff --git a/apps/web/src/components/plans/wizard/CadenceCalendar.tsx b/apps/web/src/components/plans/wizard/CadenceCalendar.tsx
new file mode 100644
index 0000000..3073e0b
--- /dev/null
+++ b/apps/web/src/components/plans/wizard/CadenceCalendar.tsx
@@ -0,0 +1,530 @@
+import { useMemo } from "react";
+import { Plus, Trash2, AlertTriangle } from "lucide-react";
+import type { RelanceTone } from "@rubis/shared";
+
+import { cn } from "@/lib/utils";
+import { TONE_LABELS } from "@/lib/plans";
+import { Field } from "@/components/ui/Field";
+import { Input } from "@/components/ui/Input";
+import { Button } from "@/components/ui/Button";
+
+/**
+ * Étape minimale pour la visu calendrier (subject/body arrivent à
+ * l'étape 3 du wizard).
+ */
+export type DraftStepLite = {
+ offsetDays: number;
+ tone: RelanceTone;
+ requiresManualValidation?: boolean;
+};
+
+/**
+ * Calendrier mensuel comme visualisation principale de la cadence.
+ *
+ * - L'échéance fictive sert de repère héros (◆ rubis plein) pour que
+ * l'utilisateur se projette dans le timing réel.
+ * - Chaque étape s'affiche sur la case du jour J+offset, colorée par
+ * tonalité.
+ * - Cliquer une case = sélectionner l'étape, l'éditeur (offset + ton +
+ * supprimer) apparaît directement en dessous.
+ * - On rend tous les mois entre la première et la dernière étape, plus
+ * le mois de l'échéance : on évite de scroller un calendrier vide.
+ *
+ * Date de référence : le 15 du mois prochain. Stable, prévisible,
+ * laisse de la marge pour les offsets négatifs (rappel avant échéance)
+ * comme positifs (mises en demeure J+30, J+60).
+ */
+export function CadenceCalendar({
+ steps,
+ selectedIndex = -1,
+ onSelectStep,
+ onUpdateStep,
+ onAddStep,
+ onRemoveStep,
+}: {
+ steps: DraftStepLite[];
+ selectedIndex?: number;
+ onSelectStep?: (idx: number) => void;
+ onUpdateStep?: (idx: number, patch: Partial) => void;
+ onAddStep: () => void;
+ onRemoveStep: (idx: number) => void;
+}) {
+ const dueDate = useMemo(() => {
+ const now = new Date();
+ return new Date(now.getFullYear(), now.getMonth() + 1, 15);
+ }, []);
+
+ const stepDates = useMemo(
+ () => steps.map((s) => addDays(dueDate, s.offsetDays)),
+ [steps, dueDate],
+ );
+
+ const months = useMemo(() => listMonths(dueDate, stepDates), [dueDate, stepDates]);
+
+ const selected = selectedIndex >= 0 ? steps[selectedIndex] : null;
+
+ return (
+
+ {/* Header */}
+
+
+
+ Calendrier
+
+
+ Pour une facture échue le{" "}
+
+ {formatLongDate(dueDate)}
+
+ , voici ce que Rubis enverra.
+