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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user