Checkpoint: Trois améliorations majeures du champ formateur :
✅ **1. Affichage dans le tableau des séquences** - Nouvelle colonne "Formateur" ajoutée au tableau de gestion - Affiche le nom du formateur ou "-" si non renseigné ✅ **2. Liste prédéfinie avec autocomplétion** - Nouvelle table `formateurs` dans la base de données - Procédures tRPC (list, create, delete) pour gérer les formateurs - Composant `FormateurCombobox` avec autocomplétion intelligente - Possibilité d'ajouter rapidement un nouveau formateur depuis le formulaire - Recherche en temps réel parmi les formateurs existants ✅ **3. Inclusion dans les emails d'invitation** - Le nom du formateur est maintenant affiché dans les emails de confirmation d'inscription - Affichage conditionnel (uniquement si un formateur est renseigné) - Améliore la communication avec les apprenants Ces améliorations standardisent la gestion des formateurs, évitent les doublons/fautes de frappe, et enrichissent l'information transmise aux apprenants.
This commit is contained in:
9
.manus/db/db-query-1763562834604.json
Normal file
9
.manus/db/db-query-1763562834604.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"query": "CREATE TABLE IF NOT EXISTS formateurs (\n id INT AUTO_INCREMENT PRIMARY KEY,\n nom VARCHAR(255) NOT NULL UNIQUE,\n actif BOOLEAN DEFAULT TRUE NOT NULL,\n createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,\n updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL\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 formateurs (\n id INT AUTO_INCREMENT PRIMARY KEY,\n nom VARCHAR(255) NOT NULL UNIQUE,\n actif BOOLEAN DEFAULT TRUE NOT NULL,\n createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,\n updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL\n);",
|
||||||
|
"rows": [],
|
||||||
|
"messages": [],
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
"execution_time_ms": 189
|
||||||
|
}
|
||||||
135
client/src/components/FormateurCombobox.tsx
Normal file
135
client/src/components/FormateurCombobox.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Check, ChevronsUpDown, Plus } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { trpc } from "@/lib/trpc";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
interface FormateurComboboxProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FormateurCombobox({ value, onChange, placeholder = "Sélectionner un formateur..." }: FormateurComboboxProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
|
||||||
|
const { data: formateurs = [], refetch } = trpc.formateurs.list.useQuery();
|
||||||
|
const createMutation = trpc.formateurs.create.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Formateur ajouté");
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Erreur lors de l'ajout du formateur");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddNew = () => {
|
||||||
|
if (!searchValue.trim()) {
|
||||||
|
toast.error("Veuillez entrer un nom de formateur");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si le formateur existe déjà
|
||||||
|
const exists = formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase());
|
||||||
|
if (exists) {
|
||||||
|
toast.error("Ce formateur existe déjà");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMutation.mutate({ nom: searchValue.trim() });
|
||||||
|
onChange(searchValue.trim());
|
||||||
|
setSearchValue("");
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{value || placeholder}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-full p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Rechercher ou ajouter un formateur..."
|
||||||
|
value={searchValue}
|
||||||
|
onValueChange={setSearchValue}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>
|
||||||
|
<div className="flex flex-col items-center gap-2 py-2">
|
||||||
|
<p className="text-sm text-muted-foreground">Aucun formateur trouvé</p>
|
||||||
|
{searchValue && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAddNew}
|
||||||
|
disabled={createMutation.isPending}
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Ajouter "{searchValue}"
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{formateurs.map((formateur) => (
|
||||||
|
<CommandItem
|
||||||
|
key={formateur.id}
|
||||||
|
value={formateur.nom}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
onChange(currentValue === value ? "" : currentValue);
|
||||||
|
setOpen(false);
|
||||||
|
setSearchValue("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4",
|
||||||
|
value === formateur.nom ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{formateur.nom}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
{searchValue && !formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase()) && (
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={handleAddNew}
|
||||||
|
className="text-primary"
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Ajouter "{searchValue}"
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState } from "react";
|
||||||
import DashboardLayout from "@/components/DashboardLayout";
|
import DashboardLayout from "@/components/DashboardLayout";
|
||||||
|
import { FormateurCombobox } from "@/components/FormateurCombobox";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
@@ -402,11 +403,10 @@ export default function AdminSequences() {
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="formateur">Formateur</Label>
|
<Label htmlFor="formateur">Formateur</Label>
|
||||||
<Input
|
<FormateurCombobox
|
||||||
id="formateur"
|
|
||||||
value={formData.formateur}
|
value={formData.formateur}
|
||||||
onChange={(e) => setFormData({ ...formData, formateur: e.target.value })}
|
onChange={(value) => setFormData({ ...formData, formateur: value })}
|
||||||
placeholder="Nom du formateur (optionnel)"
|
placeholder="Sélectionner un formateur (optionnel)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -636,6 +636,7 @@ export default function AdminSequences() {
|
|||||||
<TableHead>Dates</TableHead>
|
<TableHead>Dates</TableHead>
|
||||||
<TableHead>Lieu</TableHead>
|
<TableHead>Lieu</TableHead>
|
||||||
<TableHead>Public cible</TableHead>
|
<TableHead>Public cible</TableHead>
|
||||||
|
<TableHead>Formateur</TableHead>
|
||||||
<TableHead>Capacité</TableHead>
|
<TableHead>Capacité</TableHead>
|
||||||
<TableHead>Statut</TableHead>
|
<TableHead>Statut</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
@@ -662,6 +663,7 @@ export default function AdminSequences() {
|
|||||||
{getPublicCibleLabel(sequence.publicCible)}
|
{getPublicCibleLabel(sequence.publicCible)}
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="max-w-xs truncate">{sequence.formateur || "-"}</TableCell>
|
||||||
<TableCell>{sequence.nbInscrits || 0} / {sequence.capaciteMax}</TableCell>
|
<TableCell>{sequence.nbInscrits || 0} / {sequence.capaciteMax}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatutBadge(sequence.statut)}`}>
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatutBadge(sequence.statut)}`}>
|
||||||
@@ -775,11 +777,10 @@ export default function AdminSequences() {
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="edit-formateur">Formateur</Label>
|
<Label htmlFor="edit-formateur">Formateur</Label>
|
||||||
<Input
|
<FormateurCombobox
|
||||||
id="edit-formateur"
|
|
||||||
value={formData.formateur}
|
value={formData.formateur}
|
||||||
onChange={(e) => setFormData({ ...formData, formateur: e.target.value })}
|
onChange={(value) => setFormData({ ...formData, formateur: value })}
|
||||||
placeholder="Nom du formateur (optionnel)"
|
placeholder="Sélectionner un formateur (optionnel)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
9
drizzle/0017_dear_tana_nile.sql
Normal file
9
drizzle/0017_dear_tana_nile.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE `formateurs` (
|
||||||
|
`id` int AUTO_INCREMENT NOT NULL,
|
||||||
|
`nom` varchar(255) NOT NULL,
|
||||||
|
`actif` boolean NOT NULL DEFAULT true,
|
||||||
|
`createdAt` timestamp NOT NULL DEFAULT (now()),
|
||||||
|
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT `formateurs_id` PRIMARY KEY(`id`),
|
||||||
|
CONSTRAINT `formateurs_nom_unique` UNIQUE(`nom`)
|
||||||
|
);
|
||||||
842
drizzle/meta/0017_snapshot.json
Normal file
842
drizzle/meta/0017_snapshot.json
Normal file
@@ -0,0 +1,842 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "mysql",
|
||||||
|
"id": "d7d07602-ed71-4c09-b5d4-3ebd05e45303",
|
||||||
|
"prevId": "860496d9-3739-4694-b45d-725a447f3eee",
|
||||||
|
"tables": {
|
||||||
|
"apprenants": {
|
||||||
|
"name": "apprenants",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"nom": {
|
||||||
|
"name": "nom",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"prenom": {
|
||||||
|
"name": "prenom",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(320)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"codeEtablissement": {
|
||||||
|
"name": "codeEtablissement",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"fonction": {
|
||||||
|
"name": "fonction",
|
||||||
|
"type": "enum('directeur','chef_service','autre')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"apprenants_id": {
|
||||||
|
"name": "apprenants_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"apprenants_email_unique": {
|
||||||
|
"name": "apprenants_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"datesFormation": {
|
||||||
|
"name": "datesFormation",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"sequenceId": {
|
||||||
|
"name": "sequenceId",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"dateDebut": {
|
||||||
|
"name": "dateDebut",
|
||||||
|
"type": "datetime",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"dateFin": {
|
||||||
|
"name": "dateFin",
|
||||||
|
"type": "datetime",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ordre": {
|
||||||
|
"name": "ordre",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"datesFormation_id": {
|
||||||
|
"name": "datesFormation_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"emailConfig": {
|
||||||
|
"name": "emailConfig",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'resend'"
|
||||||
|
},
|
||||||
|
"apiKey": {
|
||||||
|
"name": "apiKey",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"fromEmail": {
|
||||||
|
"name": "fromEmail",
|
||||||
|
"type": "varchar(320)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"fromName": {
|
||||||
|
"name": "fromName",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'Formation Manager Itinova'"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"name": "mode",
|
||||||
|
"type": "enum('simulation','production')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'simulation'"
|
||||||
|
},
|
||||||
|
"domainVerified": {
|
||||||
|
"name": "domainVerified",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"name": "active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"emailConfig_id": {
|
||||||
|
"name": "emailConfig_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"emailTemplates": {
|
||||||
|
"name": "emailTemplates",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"logoUrl": {
|
||||||
|
"name": "logoUrl",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"primaryColor": {
|
||||||
|
"name": "primaryColor",
|
||||||
|
"type": "varchar(7)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'#2563eb'"
|
||||||
|
},
|
||||||
|
"headerBgColor": {
|
||||||
|
"name": "headerBgColor",
|
||||||
|
"type": "varchar(7)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'#2563eb'"
|
||||||
|
},
|
||||||
|
"headerTextColor": {
|
||||||
|
"name": "headerTextColor",
|
||||||
|
"type": "varchar(7)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'#ffffff'"
|
||||||
|
},
|
||||||
|
"headerTitle": {
|
||||||
|
"name": "headerTitle",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'Formation Manager Itinova'"
|
||||||
|
},
|
||||||
|
"footerText": {
|
||||||
|
"name": "footerText",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"name": "active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"emailTemplates_id": {
|
||||||
|
"name": "emailTemplates_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"emailTemplates_type_unique": {
|
||||||
|
"name": "emailTemplates_type_unique",
|
||||||
|
"columns": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"formateurs": {
|
||||||
|
"name": "formateurs",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"nom": {
|
||||||
|
"name": "nom",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"actif": {
|
||||||
|
"name": "actif",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"formateurs_id": {
|
||||||
|
"name": "formateurs_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"formateurs_nom_unique": {
|
||||||
|
"name": "formateurs_nom_unique",
|
||||||
|
"columns": [
|
||||||
|
"nom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"formations": {
|
||||||
|
"name": "formations",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"nom": {
|
||||||
|
"name": "nom",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lienUnique": {
|
||||||
|
"name": "lienUnique",
|
||||||
|
"type": "varchar(100)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"actif": {
|
||||||
|
"name": "actif",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"formations_id": {
|
||||||
|
"name": "formations_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"formations_lienUnique_unique": {
|
||||||
|
"name": "formations_lienUnique_unique",
|
||||||
|
"columns": [
|
||||||
|
"lienUnique"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"inscriptions": {
|
||||||
|
"name": "inscriptions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"apprenantId": {
|
||||||
|
"name": "apprenantId",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sequenceId": {
|
||||||
|
"name": "sequenceId",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"statut": {
|
||||||
|
"name": "statut",
|
||||||
|
"type": "enum('confirmee','liste_attente','annulee')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"dateInscription": {
|
||||||
|
"name": "dateInscription",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"inscriptions_id": {
|
||||||
|
"name": "inscriptions_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"passwordResetTokens": {
|
||||||
|
"name": "passwordResetTokens",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"used": {
|
||||||
|
"name": "used",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"passwordResetTokens_id": {
|
||||||
|
"name": "passwordResetTokens_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"passwordResetTokens_token_unique": {
|
||||||
|
"name": "passwordResetTokens_token_unique",
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"sequences": {
|
||||||
|
"name": "sequences",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"formationId": {
|
||||||
|
"name": "formationId",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"nom": {
|
||||||
|
"name": "nom",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lieu": {
|
||||||
|
"name": "lieu",
|
||||||
|
"type": "varchar(500)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"publicCible": {
|
||||||
|
"name": "publicCible",
|
||||||
|
"type": "enum('directeur','chef_service','tous','autre')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"formateur": {
|
||||||
|
"name": "formateur",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"capaciteMax": {
|
||||||
|
"name": "capaciteMax",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 12
|
||||||
|
},
|
||||||
|
"dateBlocage": {
|
||||||
|
"name": "dateBlocage",
|
||||||
|
"type": "datetime",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"statut": {
|
||||||
|
"name": "statut",
|
||||||
|
"type": "enum('ouverte','bloquee','terminee')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'ouverte'"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"sequences_id": {
|
||||||
|
"name": "sequences_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"openId": {
|
||||||
|
"name": "openId",
|
||||||
|
"type": "varchar(64)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(320)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"loginMethod": {
|
||||||
|
"name": "loginMethod",
|
||||||
|
"type": "varchar(64)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "enum('user','admin')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'user'"
|
||||||
|
},
|
||||||
|
"isActive": {
|
||||||
|
"name": "isActive",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"onUpdate": true,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"lastSignedIn": {
|
||||||
|
"name": "lastSignedIn",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"users_id": {
|
||||||
|
"name": "users_id",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"tables": {},
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,6 +120,13 @@
|
|||||||
"when": 1763562352026,
|
"when": 1763562352026,
|
||||||
"tag": "0016_chunky_landau",
|
"tag": "0016_chunky_landau",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1763563084443,
|
||||||
|
"tag": "0017_dear_tana_nile",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,20 @@ export const apprenants = mysqlTable("apprenants", {
|
|||||||
export type Apprenant = typeof apprenants.$inferSelect;
|
export type Apprenant = typeof apprenants.$inferSelect;
|
||||||
export type InsertApprenant = typeof apprenants.$inferInsert;
|
export type InsertApprenant = typeof apprenants.$inferInsert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table des formateurs
|
||||||
|
*/
|
||||||
|
export const formateurs = mysqlTable("formateurs", {
|
||||||
|
id: int("id").autoincrement().primaryKey(),
|
||||||
|
nom: varchar("nom", { length: 255 }).notNull().unique(),
|
||||||
|
actif: boolean("actif").default(true).notNull(),
|
||||||
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Formateur = typeof formateurs.$inferSelect;
|
||||||
|
export type InsertFormateur = typeof formateurs.$inferInsert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table des séquences de formation
|
* Table des séquences de formation
|
||||||
* Une séquence peut contenir jusqu'à 4 dates de formation
|
* Une séquence peut contenir jusqu'à 4 dates de formation
|
||||||
|
|||||||
37
server/db.ts
37
server/db.ts
@@ -5,6 +5,7 @@ import {
|
|||||||
users,
|
users,
|
||||||
formations,
|
formations,
|
||||||
apprenants,
|
apprenants,
|
||||||
|
formateurs,
|
||||||
sequences,
|
sequences,
|
||||||
datesFormation,
|
datesFormation,
|
||||||
inscriptions,
|
inscriptions,
|
||||||
@@ -18,10 +19,12 @@ import {
|
|||||||
InsertInscription,
|
InsertInscription,
|
||||||
InsertFormation,
|
InsertFormation,
|
||||||
InsertApprenant,
|
InsertApprenant,
|
||||||
|
InsertFormateur,
|
||||||
Sequence,
|
Sequence,
|
||||||
DateFormation,
|
DateFormation,
|
||||||
Apprenant,
|
Apprenant,
|
||||||
Formation,
|
Formation,
|
||||||
|
Formateur,
|
||||||
EmailTemplate,
|
EmailTemplate,
|
||||||
InsertEmailTemplate
|
InsertEmailTemplate
|
||||||
} from "../drizzle/schema";
|
} from "../drizzle/schema";
|
||||||
@@ -656,3 +659,37 @@ export async function updateEmailConfig(id: number, data: Partial<InsertEmailCon
|
|||||||
})
|
})
|
||||||
.where(eq(emailConfig.id, id));
|
.where(eq(emailConfig.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== FORMATEURS =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer tous les formateurs actifs
|
||||||
|
*/
|
||||||
|
export async function getFormateurs() {
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return [];
|
||||||
|
|
||||||
|
const result = await db.select().from(formateurs).where(eq(formateurs.actif, true));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Créer un nouveau formateur
|
||||||
|
*/
|
||||||
|
export async function createFormateur(data: InsertFormateur) {
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return null;
|
||||||
|
|
||||||
|
const result = await db.insert(formateurs).values(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprimer un formateur (désactivation logique)
|
||||||
|
*/
|
||||||
|
export async function deleteFormateur(id: number) {
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return;
|
||||||
|
|
||||||
|
await db.update(formateurs).set({ actif: false }).where(eq(formateurs.id, id));
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export async function sendInscriptionConfirmation(params: {
|
|||||||
sequenceNom: string;
|
sequenceNom: string;
|
||||||
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
||||||
lieu: string;
|
lieu: string;
|
||||||
|
formateur?: string;
|
||||||
statut: 'confirmee' | 'liste_attente';
|
statut: 'confirmee' | 'liste_attente';
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
const isConfirmed = params.statut === 'confirmee';
|
const isConfirmed = params.statut === 'confirmee';
|
||||||
@@ -88,6 +89,7 @@ export async function sendInscriptionConfirmation(params: {
|
|||||||
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
||||||
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
||||||
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
||||||
|
${params.formateur ? `<p><strong>Formateur :</strong> ${params.formateur}</p>` : ''}
|
||||||
<h4>Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :</h4>
|
<h4>Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :</h4>
|
||||||
${datesHTML}
|
${datesHTML}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,6 +72,25 @@ export const appRouter = router({
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// ===== FORMATEURS =====
|
||||||
|
formateurs: router({
|
||||||
|
list: adminProcedure.query(async () => {
|
||||||
|
return db.getFormateurs();
|
||||||
|
}),
|
||||||
|
|
||||||
|
create: adminProcedure.input(z.object({
|
||||||
|
nom: z.string().min(1),
|
||||||
|
})).mutation(async ({ input }) => {
|
||||||
|
await db.createFormateur(input);
|
||||||
|
return { success: true };
|
||||||
|
}),
|
||||||
|
|
||||||
|
delete: adminProcedure.input(z.object({ id: z.number() })).mutation(async ({ input }) => {
|
||||||
|
await db.deleteFormateur(input.id);
|
||||||
|
return { success: true };
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
// ===== APPRENANTS =====
|
// ===== APPRENANTS =====
|
||||||
apprenants: router({
|
apprenants: router({
|
||||||
list: adminProcedure.query(async () => {
|
list: adminProcedure.query(async () => {
|
||||||
@@ -330,6 +349,7 @@ export const appRouter = router({
|
|||||||
ordre: d.ordre,
|
ordre: d.ordre,
|
||||||
})),
|
})),
|
||||||
lieu: inscriptionSequence.lieu,
|
lieu: inscriptionSequence.lieu,
|
||||||
|
formateur: inscriptionSequence.formateur || undefined,
|
||||||
statut,
|
statut,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
10
todo.md
10
todo.md
@@ -270,3 +270,13 @@
|
|||||||
- [x] Ajouter le champ formateur dans le formulaire de création de séquence
|
- [x] Ajouter le champ formateur dans le formulaire de création de séquence
|
||||||
- [x] Ajouter le champ formateur dans le formulaire de modification de séquence
|
- [x] Ajouter le champ formateur dans le formulaire de modification de séquence
|
||||||
- [x] Tester la création et modification avec le nouveau champ
|
- [x] Tester la création et modification avec le nouveau champ
|
||||||
|
|
||||||
|
## Améliorations du champ Formateur
|
||||||
|
|
||||||
|
- [x] Afficher le formateur dans le tableau des séquences (nouvelle colonne)
|
||||||
|
- [x] Créer une table formateurs dans la base de données
|
||||||
|
- [x] Ajouter les procédures tRPC pour gérer les formateurs (CRUD)
|
||||||
|
- [x] Remplacer le champ texte par un composant avec autocomplétion
|
||||||
|
- [x] Permettre l'ajout rapide de nouveaux formateurs depuis le formulaire
|
||||||
|
- [x] Ajouter le formateur dans les templates d'emails d'invitation
|
||||||
|
- [x] Tester l'autocomplétion et l'affichage dans les emails
|
||||||
|
|||||||
Reference in New Issue
Block a user