Checkpoint: Modification de la fenêtre de création des rappels

## Modifications effectuées :

### Base de données (drizzle/schema.ts)
Ajout d'un champ `timing` dans la table `rappels` :
- Type : ENUM('pre_formation', 'post_formation')
- Valeur par défaut : 'pre_formation'
- Permet de distinguer les rappels avant et après formation

### Interface utilisateur (client/src/pages/AdminRappels.tsx)
**Formulaire de création et d'édition :**
- Ajout de radio buttons pour choisir "Pré-formation" ou "Post-formation"
- Le label du champ "Nombre de jours" change dynamiquement selon le choix :
  * "Nombre de jours avant la séquence" (pré-formation)
  * "Nombre de jours après la séquence" (post-formation)
- Le texte d'aide s'adapte également au timing choisi

**États et formulaires :**
- Ajout du champ `timing` dans `formData`
- Mise à jour de `resetForm()` et `handleEdit()` pour gérer le nouveau champ

### Backend (server/routers.ts)
Mise à jour des procédures tRPC :
- `rappels.create` : ajout du champ `timing` avec validation Zod
- `rappels.update` : ajout du champ `timing` optionnel

## Fonctionnement
L'utilisateur peut maintenant créer des rappels qui seront envoyés soit avant (pré-formation) soit après (post-formation) une séquence, avec un nombre de jours personnalisable pour chaque cas.
This commit is contained in:
Manus
2026-01-08 05:38:52 -05:00
parent 1cee07582f
commit 6e25c00865
7 changed files with 2154 additions and 9 deletions

View File

@@ -0,0 +1,9 @@
{
"query": "ALTER TABLE rappels ADD COLUMN timing ENUM('pre_formation', 'post_formation') NOT NULL DEFAULT 'pre_formation' AFTER templateType;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE rappels ADD COLUMN timing ENUM('pre_formation', 'post_formation') NOT NULL DEFAULT 'pre_formation' AFTER templateType;",
"rows": [],
"messages": [],
"stdout": "",
"stderr": "",
"execution_time_ms": 1118
}

View File

@@ -42,6 +42,7 @@ export default function AdminRappels() {
const [formData, setFormData] = useState({
nom: "",
templateType: "",
timing: "pre_formation" as "pre_formation" | "post_formation",
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
@@ -97,6 +98,7 @@ export default function AdminRappels() {
setFormData({
nom: "",
templateType: "",
timing: "pre_formation",
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
@@ -116,6 +118,7 @@ export default function AdminRappels() {
setFormData({
nom: rappel.nom,
templateType: rappel.templateType,
timing: rappel.timing || "pre_formation",
joursAvant: rappel.joursAvant,
heureEnvoi: rappel.heureEnvoi,
actif: rappel.actif,
@@ -292,19 +295,53 @@ export default function AdminRappels() {
</div>
<div className="space-y-2">
<Label htmlFor="joursAvant">Nombre de jours avant la séquence *</Label>
<Label>Timing d'envoi *</Label>
<div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="timing"
value="pre_formation"
checked={formData.timing === "pre_formation"}
onChange={(e) => setFormData({ ...formData, timing: e.target.value as "pre_formation" | "post_formation" })}
className="w-4 h-4"
/>
<span>Pré-formation</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="timing"
value="post_formation"
checked={formData.timing === "post_formation"}
onChange={(e) => setFormData({ ...formData, timing: e.target.value as "pre_formation" | "post_formation" })}
className="w-4 h-4"
/>
<span>Post-formation</span>
</label>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="joursAvant">
{formData.timing === "pre_formation"
? "Nombre de jours avant la séquence *"
: "Nombre de jours après la séquence *"}
</Label>
<Input
id="joursAvant"
type="number"
min="0"
min="1"
value={formData.joursAvant}
onChange={(e) =>
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 0 })
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 })
}
placeholder="Ex: 7"
/>
<p className="text-sm text-muted-foreground">
Le rappel sera envoyé ce nombre de jours avant le début de la séquence
{formData.timing === "pre_formation"
? "Le rappel sera envoyé ce nombre de jours avant le début de la séquence"
: "Le rappel sera envoyé ce nombre de jours après la fin de la séquence"}
</p>
</div>
@@ -381,17 +418,54 @@ export default function AdminRappels() {
</div>
<div className="space-y-2">
<Label htmlFor="edit-joursAvant">Nombre de jours avant la séquence *</Label>
<Label>Timing d'envoi *</Label>
<div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="edit-timing"
value="pre_formation"
checked={formData.timing === "pre_formation"}
onChange={(e) => setFormData({ ...formData, timing: e.target.value as "pre_formation" | "post_formation" })}
className="w-4 h-4"
/>
<span>Pré-formation</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="edit-timing"
value="post_formation"
checked={formData.timing === "post_formation"}
onChange={(e) => setFormData({ ...formData, timing: e.target.value as "pre_formation" | "post_formation" })}
className="w-4 h-4"
/>
<span>Post-formation</span>
</label>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="edit-joursAvant">
{formData.timing === "pre_formation"
? "Nombre de jours avant la séquence *"
: "Nombre de jours après la séquence *"}
</Label>
<Input
id="edit-joursAvant"
type="number"
min="0"
min="1"
value={formData.joursAvant}
onChange={(e) =>
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 0 })
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 })
}
placeholder="Ex: 7"
/>
<p className="text-sm text-muted-foreground">
{formData.timing === "pre_formation"
? "Le rappel sera envoyé ce nombre de jours avant le début de la séquence"
: "Le rappel sera envoyé ce nombre de jours après la fin de la séquence"}
</p>
</div>
<div className="space-y-2">

File diff suppressed because it is too large Load Diff

View File

@@ -197,6 +197,13 @@
"when": 1767799408213,
"tag": "0027_naive_jackal",
"breakpoints": true
},
{
"idx": 28,
"version": "5",
"when": 1767868505577,
"tag": "0028_military_pandemic",
"breakpoints": true
}
]
}

View File

@@ -276,9 +276,11 @@ export const rappels = mysqlTable("rappels", {
id: int("id").autoincrement().primaryKey(),
/** Nom du rappel */
nom: varchar("nom", { length: 255 }).notNull(),
/** Type de template à utiliser (rappel, rappelJ1) */
/** Type de template à utiliser (rappel, rappelJ1, etc.) */
templateType: varchar("templateType", { length: 50 }).notNull(),
/** Nombre de jours avant la première date de la séquence */
/** Timing : 'pre_formation' ou 'post_formation' */
timing: mysqlEnum("timing", ["pre_formation", "post_formation"]).default("pre_formation").notNull(),
/** Nombre de jours avant (positif) ou après (négatif) la première date de la séquence */
joursAvant: int("joursAvant").notNull(),
/** Heure d'envoi (format HH:MM) */
heureEnvoi: varchar("heureEnvoi", { length: 5 }).default("09:00").notNull(),

View File

@@ -1066,6 +1066,7 @@ export const appRouter = router({
.input(z.object({
nom: z.string(),
templateType: z.string(),
timing: z.enum(["pre_formation", "post_formation"]).default("pre_formation"),
joursAvant: z.number(),
heureEnvoi: z.string().default("09:00"),
actif: z.boolean(),
@@ -1079,6 +1080,7 @@ export const appRouter = router({
id: z.number(),
nom: z.string().optional(),
templateType: z.string().optional(),
timing: z.enum(["pre_formation", "post_formation"]).optional(),
joursAvant: z.number().optional(),
heureEnvoi: z.string().optional(),
actif: z.boolean().optional(),

View File

@@ -515,3 +515,11 @@
## Correction erreur emailTemplates.getByType
- [x] Modifier getEmailTemplateByType dans db.ts pour retourner null au lieu de undefined
## Modification de la fenêtre de création des rappels
- [x] Analyser le schéma actuel de la table rappels
- [x] Ajouter un champ "timing" (pre_formation / post_formation) dans le schéma
- [x] Appliquer la modification en base de données (ALTER TABLE)
- [x] Modifier AdminRappels.tsx pour ajouter les radio buttons pré/post-formation
- [x] Ajouter un champ de saisie du nombre de jours avec label dynamique
- [x] Mettre à jour les procédures tRPC (create et update) pour gérer timing