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:
9
.manus/db/db-query-1764540522642.json
Normal file
9
.manus/db/db-query-1764540522642.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `rappels` (\n `id` int AUTO_INCREMENT NOT NULL,\n `nom` varchar(255) NOT NULL,\n `templateType` varchar(50) NOT NULL,\n `joursAvant` int NOT NULL,\n `heureEnvoi` varchar(5) NOT NULL DEFAULT '09:00',\n `actif` boolean NOT NULL DEFAULT true,\n `derniereExecution` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `rappels_id` PRIMARY KEY(`id`)\n)",
|
||||
"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 CREATE TABLE IF NOT EXISTS `rappels` (\n `id` int AUTO_INCREMENT NOT NULL,\n `nom` varchar(255) NOT NULL,\n `templateType` varchar(50) NOT NULL,\n `joursAvant` int NOT NULL,\n `heureEnvoi` varchar(5) NOT NULL DEFAULT '09:00',\n `actif` boolean NOT NULL DEFAULT true,\n `derniereExecution` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `rappels_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 112
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
12
drizzle/0017_unique_skrulls.sql
Normal file
12
drizzle/0017_unique_skrulls.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE `rappels` (
|
||||
`id` int AUTO_INCREMENT NOT NULL,
|
||||
`nom` varchar(255) NOT NULL,
|
||||
`templateType` varchar(50) NOT NULL,
|
||||
`joursAvant` int NOT NULL,
|
||||
`heureEnvoi` varchar(5) NOT NULL DEFAULT '09:00',
|
||||
`actif` boolean NOT NULL DEFAULT true,
|
||||
`derniereExecution` timestamp,
|
||||
`createdAt` timestamp NOT NULL DEFAULT (now()),
|
||||
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT `rappels_id` PRIMARY KEY(`id`)
|
||||
);
|
||||
1087
drizzle/meta/0017_snapshot.json
Normal file
1087
drizzle/meta/0017_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -120,6 +120,13 @@
|
||||
"when": 1764329348207,
|
||||
"tag": "0016_romantic_sinister_six",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "5",
|
||||
"when": 1764540514199,
|
||||
"tag": "0017_unique_skrulls",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -263,3 +263,28 @@ export const alertes = mysqlTable("alertes", {
|
||||
|
||||
export type Alerte = typeof alertes.$inferSelect;
|
||||
export type InsertAlerte = typeof alertes.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des rappels automatiques
|
||||
* Stocke les règles d'envoi automatique de rappels avant les séquences
|
||||
*/
|
||||
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) */
|
||||
templateType: varchar("templateType", { length: 50 }).notNull(),
|
||||
/** Nombre de jours avant 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(),
|
||||
/** Actif ou non */
|
||||
actif: boolean("actif").default(true).notNull(),
|
||||
/** Dernière exécution */
|
||||
derniereExecution: timestamp("derniereExecution"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Rappel = typeof rappels.$inferSelect;
|
||||
export type InsertRappel = typeof rappels.$inferInsert;
|
||||
|
||||
72
server/db.ts
72
server/db.ts
@@ -26,7 +26,10 @@ import {
|
||||
InsertEmailTemplate,
|
||||
formateurs,
|
||||
InsertFormateur,
|
||||
Formateur
|
||||
Formateur,
|
||||
rappels,
|
||||
InsertRappel,
|
||||
Rappel
|
||||
} from "../drizzle/schema";
|
||||
import { ENV } from './_core/env';
|
||||
|
||||
@@ -816,3 +819,70 @@ export async function deleteFormateur(id: number) {
|
||||
|
||||
await db.delete(formateurs).where(eq(formateurs.id, id));
|
||||
}
|
||||
|
||||
|
||||
// ==================== RAPPELS ====================
|
||||
|
||||
export async function createRappel(data: InsertRappel) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Construire un objet d'insertion explicite sans derniereExecution
|
||||
const insertData: Partial<InsertRappel> = {
|
||||
nom: data.nom,
|
||||
templateType: data.templateType,
|
||||
joursAvant: data.joursAvant,
|
||||
heureEnvoi: data.heureEnvoi,
|
||||
actif: data.actif,
|
||||
};
|
||||
const result = await db.insert(rappels).values(insertData as InsertRappel);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getRappels() {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
|
||||
return await db.select().from(rappels).orderBy(rappels.joursAvant);
|
||||
}
|
||||
|
||||
export async function getRappelById(id: number): Promise<Rappel | undefined> {
|
||||
const db = await getDb();
|
||||
if (!db) return undefined;
|
||||
|
||||
const result = await db.select().from(rappels).where(eq(rappels.id, id)).limit(1);
|
||||
return result.length > 0 ? result[0] : undefined;
|
||||
}
|
||||
|
||||
export async function getActiveRappels(): Promise<Rappel[]> {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
|
||||
return await db.select().from(rappels).where(eq(rappels.actif, true));
|
||||
}
|
||||
|
||||
export async function updateRappel(id: number, data: Partial<InsertRappel>) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
await db.update(rappels).set({
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
}).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
export async function deleteRappel(id: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
await db.delete(rappels).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
export async function updateRappelExecution(id: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
await db.update(rappels).set({
|
||||
derniereExecution: new Date(),
|
||||
}).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
@@ -829,15 +829,15 @@ export const appRouter = router({
|
||||
// ===== GESTION DES RAPPELS =====
|
||||
rappels: router({
|
||||
list: adminProcedure.query(async () => {
|
||||
return db.getAllRappels();
|
||||
return db.getRappels();
|
||||
}),
|
||||
|
||||
create: adminProcedure
|
||||
.input(z.object({
|
||||
nom: z.string(),
|
||||
templateType: z.string(),
|
||||
joursAvant: z.number(),
|
||||
sujet: z.string(),
|
||||
contenu: z.string(),
|
||||
heureEnvoi: z.string().default("09:00"),
|
||||
actif: z.boolean(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -847,14 +847,16 @@ export const appRouter = router({
|
||||
update: adminProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
nom: z.string(),
|
||||
joursAvant: z.number(),
|
||||
sujet: z.string(),
|
||||
contenu: z.string(),
|
||||
actif: z.boolean(),
|
||||
nom: z.string().optional(),
|
||||
templateType: z.string().optional(),
|
||||
joursAvant: z.number().optional(),
|
||||
heureEnvoi: z.string().optional(),
|
||||
actif: z.boolean().optional(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
return db.updateRappel(input);
|
||||
const { id, ...data } = input;
|
||||
await db.updateRappel(id, data);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
delete: adminProcedure
|
||||
@@ -867,7 +869,7 @@ export const appRouter = router({
|
||||
toggleActif: adminProcedure
|
||||
.input(z.object({ id: z.number(), actif: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
await db.updateRappel({ id: input.id, actif: input.actif });
|
||||
await db.updateRappel(input.id, { actif: input.actif });
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
34
todo.md
34
todo.md
@@ -869,3 +869,37 @@
|
||||
- [x] Analyser le code d'envoi d'emails pour identifier pourquoi le mode simulation persiste
|
||||
- [x] Corriger le double envoi (email réel + notification simulée)
|
||||
- [ ] Tester l'envoi réel d'emails après correction
|
||||
|
||||
## Bug Rappels - Types J-7 et J-1 manquants
|
||||
|
||||
- [x] Analyser la page des rappels pour identifier pourquoi les types J-7 et J-1 n'apparaissent pas
|
||||
- [x] Ajouter les types J-7 et J-1 dans l'interface de gestion des rappels
|
||||
- [ ] Tester la création de rappels J-7 et J-1
|
||||
|
||||
## Correction template email rappel J-1
|
||||
|
||||
- [x] Ajouter le template de rappel J-1 dans la page de gestion des templates d'emails
|
||||
|
||||
## Correction template email J-7
|
||||
|
||||
- [x] Analyser le template J-7 dans la page des templates d'emails
|
||||
- [x] Analyser l'aperçu J-7 depuis les séquences
|
||||
- [x] Identifier les différences entre les deux
|
||||
- [x] Corriger le template pour qu'il corresponde à l'aperçu des séquences
|
||||
|
||||
## Amélioration template J-7
|
||||
|
||||
- [x] Modifier le template J-7 pour afficher toutes les dates de la séquence au lieu d'une seule date
|
||||
|
||||
## Bug SMTP - Mode simulation
|
||||
|
||||
- [x] Analyser le code d'envoi d'emails pour identifier pourquoi le mode simulation persiste
|
||||
- [x] Corriger le double envoi (email réel + notification simulée)
|
||||
- [x] Tester l'envoi réel d'emails après correction
|
||||
|
||||
## Bug Rappels - Types J-7 et J-1 manquants
|
||||
|
||||
- [x] Analyser la page des rappels pour identifier pourquoi les types J-7 et J-1 n'apparaissent pas
|
||||
- [x] Ajouter les types J-7 et J-1 dans l'interface de gestion des rappels
|
||||
- [ ] Corriger le bug d'insertion SQL (erreur sur derniereExecution)
|
||||
- [ ] Tester la création de rappels J-7 et J-1
|
||||
|
||||
Reference in New Issue
Block a user