Checkpoint: Ajout des types de rappels J-7 et J-1 dans l'interface de gestion des rappels. Les templates d'emails ont été corrigés pour afficher toutes les dates des séquences. Bug connu : l'insertion des rappels échoue avec une erreur SQL sur le champ derniereExecution (à corriger après ce checkpoint).

This commit is contained in:
Manus Sandbox
2025-11-30 17:24:41 -05:00
parent d84a2fe0c5
commit 548dbc12b9
9 changed files with 1375 additions and 70 deletions

View File

@@ -12,22 +12,34 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { trpc } from "@/lib/trpc";
import { Bell, Edit, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
// Types de templates disponibles pour les rappels
const TEMPLATE_TYPES = [
{ value: "rappel", label: "Rappel J-7", joursAvant: 7 },
{ value: "rappelJ1", label: "Rappel J-1", joursAvant: 1 },
];
export default function AdminRappels() {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);
const [editingRappel, setEditingRappel] = useState<any>(null);
const [formData, setFormData] = useState({
nom: "",
joursAvant: 0,
sujet: "",
contenu: "",
templateType: "",
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
});
@@ -67,18 +79,28 @@ export default function AdminRappels() {
},
});
const toggleActifMutation = trpc.rappels.toggleActif.useMutation({
onSuccess: () => {
toast.success("Statut modifié avec succès");
refetch();
},
onError: (error) => {
toast.error(`Erreur: ${error.message}`);
},
});
const resetForm = () => {
setFormData({
nom: "",
joursAvant: 0,
sujet: "",
contenu: "",
templateType: "",
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
});
};
const handleCreate = () => {
if (!formData.nom || !formData.sujet || !formData.contenu) {
if (!formData.nom || !formData.templateType) {
toast.error("Veuillez remplir tous les champs obligatoires");
return;
}
@@ -89,9 +111,9 @@ export default function AdminRappels() {
setEditingRappel(rappel);
setFormData({
nom: rappel.nom,
templateType: rappel.templateType,
joursAvant: rappel.joursAvant,
sujet: rappel.sujet,
contenu: rappel.contenu,
heureEnvoi: rappel.heureEnvoi,
actif: rappel.actif,
});
setIsEditOpen(true);
@@ -99,7 +121,7 @@ export default function AdminRappels() {
const handleUpdate = () => {
if (!editingRappel) return;
if (!formData.nom || !formData.sujet || !formData.contenu) {
if (!formData.nom || !formData.templateType) {
toast.error("Veuillez remplir tous les champs obligatoires");
return;
}
@@ -115,6 +137,24 @@ export default function AdminRappels() {
}
};
const handleToggleActif = (id: number, actif: boolean) => {
toggleActifMutation.mutate({ id, actif: !actif });
};
const handleTemplateTypeChange = (value: string) => {
const template = TEMPLATE_TYPES.find(t => t.value === value);
setFormData({
...formData,
templateType: value,
joursAvant: template?.joursAvant || 7,
nom: template?.label || "",
});
};
const getTemplateName = (type: string) => {
return TEMPLATE_TYPES.find(t => t.value === type)?.label || type;
};
return (
<DashboardLayout>
<div className="space-y-6">
@@ -146,8 +186,9 @@ export default function AdminRappels() {
<TableHeader>
<TableRow>
<TableHead>Nom</TableHead>
<TableHead>Type</TableHead>
<TableHead>Délai</TableHead>
<TableHead>Sujet</TableHead>
<TableHead>Heure</TableHead>
<TableHead>Statut</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
@@ -157,18 +198,20 @@ export default function AdminRappels() {
rappels.map((rappel) => (
<TableRow key={rappel.id}>
<TableCell className="font-medium">{rappel.nom}</TableCell>
<TableCell>{getTemplateName(rappel.templateType)}</TableCell>
<TableCell>J-{rappel.joursAvant}</TableCell>
<TableCell>{rappel.sujet}</TableCell>
<TableCell>{rappel.heureEnvoi}</TableCell>
<TableCell>
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
<button
onClick={() => handleToggleActif(rappel.id, rappel.actif)}
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium cursor-pointer ${
rappel.actif
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800"
? "bg-green-100 text-green-800 hover:bg-green-200"
: "bg-gray-100 text-gray-800 hover:bg-gray-200"
}`}
>
{rappel.actif ? "Actif" : "Inactif"}
</span>
</button>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
@@ -192,7 +235,7 @@ export default function AdminRappels() {
))
) : (
<TableRow>
<TableCell colSpan={5} className="text-center text-muted-foreground">
<TableCell colSpan={6} className="text-center text-muted-foreground">
Aucun rappel configuré
</TableCell>
</TableRow>
@@ -212,13 +255,35 @@ export default function AdminRappels() {
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="templateType">Type de rappel *</Label>
<Select
value={formData.templateType}
onValueChange={handleTemplateTypeChange}
>
<SelectTrigger>
<SelectValue placeholder="Sélectionnez un type de rappel" />
</SelectTrigger>
<SelectContent>
{TEMPLATE_TYPES.map((template) => (
<SelectItem key={template.value} value={template.value}>
{template.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
Le template d'email correspondant sera utilisé pour l'envoi
</p>
</div>
<div className="space-y-2">
<Label htmlFor="nom">Nom du rappel *</Label>
<Input
id="nom"
value={formData.nom}
onChange={(e) => setFormData({ ...formData, nom: e.target.value })}
placeholder="Ex: Rappel J-15"
placeholder="Ex: Rappel automatique J-7"
/>
</div>
@@ -232,7 +297,7 @@ export default function AdminRappels() {
onChange={(e) =>
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 0 })
}
placeholder="Ex: 15"
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
@@ -240,26 +305,15 @@ export default function AdminRappels() {
</div>
<div className="space-y-2">
<Label htmlFor="sujet">Sujet de l'email *</Label>
<Label htmlFor="heureEnvoi">Heure d'envoi *</Label>
<Input
id="sujet"
value={formData.sujet}
onChange={(e) => setFormData({ ...formData, sujet: e.target.value })}
placeholder="Ex: Rappel - Formation dans 15 jours"
/>
</div>
<div className="space-y-2">
<Label htmlFor="contenu">Contenu de l'email *</Label>
<Textarea
id="contenu"
value={formData.contenu}
onChange={(e) => setFormData({ ...formData, contenu: e.target.value })}
placeholder="Utilisez {{nom_apprenant}}, {{titre_formation}}, {{date_debut}}, {{lieu}} comme variables"
rows={8}
id="heureEnvoi"
type="time"
value={formData.heureEnvoi}
onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })}
/>
<p className="text-sm text-muted-foreground">
Variables disponibles : {"{"}{"{"} nom_apprenant {"}"}{"}"}, {"{"}{"{"} titre_formation {"}"}{"}"}, {"{"}{"{"} date_debut {"}"}{"}"}, {"{"}{"{"} lieu {"}"}{"}"}, {"{"}{"{"} formateur {"}"}{"}"}
L'heure à laquelle le rappel sera envoyé chaque jour
</p>
</div>
@@ -293,13 +347,32 @@ export default function AdminRappels() {
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="edit-templateType">Type de rappel *</Label>
<Select
value={formData.templateType}
onValueChange={handleTemplateTypeChange}
>
<SelectTrigger>
<SelectValue placeholder="Sélectionnez un type de rappel" />
</SelectTrigger>
<SelectContent>
{TEMPLATE_TYPES.map((template) => (
<SelectItem key={template.value} value={template.value}>
{template.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="edit-nom">Nom du rappel *</Label>
<Input
id="edit-nom"
value={formData.nom}
onChange={(e) => setFormData({ ...formData, nom: e.target.value })}
placeholder="Ex: Rappel J-15"
placeholder="Ex: Rappel automatique J-7"
/>
</div>
@@ -313,34 +386,20 @@ export default function AdminRappels() {
onChange={(e) =>
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 0 })
}
placeholder="Ex: 15"
placeholder="Ex: 7"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-sujet">Sujet de l'email *</Label>
<Label htmlFor="edit-heureEnvoi">Heure d'envoi *</Label>
<Input
id="edit-sujet"
value={formData.sujet}
onChange={(e) => setFormData({ ...formData, sujet: e.target.value })}
placeholder="Ex: Rappel - Formation dans 15 jours"
id="edit-heureEnvoi"
type="time"
value={formData.heureEnvoi}
onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-contenu">Contenu de l'email *</Label>
<Textarea
id="edit-contenu"
value={formData.contenu}
onChange={(e) => setFormData({ ...formData, contenu: e.target.value })}
placeholder="Utilisez {{nom_apprenant}}, {{titre_formation}}, {{date_debut}}, {{lieu}} comme variables"
rows={8}
/>
<p className="text-sm text-muted-foreground">
Variables disponibles : {"{"}{"{"} nom_apprenant {"}"}{"}"}, {"{"}{"{"} titre_formation {"}"}{"}"}, {"{"}{"{"} date_debut {"}"}{"}"}, {"{"}{"{"} lieu {"}"}{"}"}, {"{"}{"{"} formateur {"}"}{"}"}
</p>
</div>
<div className="flex items-center space-x-2">
<Switch
id="edit-actif"