diff --git a/.manus/db/db-query-1763562834604.json b/.manus/db/db-query-1763562834604.json new file mode 100644 index 0000000..e222708 --- /dev/null +++ b/.manus/db/db-query-1763562834604.json @@ -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 +} \ No newline at end of file diff --git a/client/src/components/FormateurCombobox.tsx b/client/src/components/FormateurCombobox.tsx new file mode 100644 index 0000000..c8ca604 --- /dev/null +++ b/client/src/components/FormateurCombobox.tsx @@ -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 ( + + + + + + + + + +
+

Aucun formateur trouvé

+ {searchValue && ( + + )} +
+
+ + {formateurs.map((formateur) => ( + { + onChange(currentValue === value ? "" : currentValue); + setOpen(false); + setSearchValue(""); + }} + > + + {formateur.nom} + + ))} + + {searchValue && !formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase()) && ( + + + + Ajouter "{searchValue}" + + + )} +
+
+
+
+ ); +} diff --git a/client/src/pages/AdminSequences.tsx b/client/src/pages/AdminSequences.tsx index f66000d..b9a4354 100644 --- a/client/src/pages/AdminSequences.tsx +++ b/client/src/pages/AdminSequences.tsx @@ -1,5 +1,6 @@ -import { useState, useMemo } from "react"; +import { useState } from "react"; import DashboardLayout from "@/components/DashboardLayout"; +import { FormateurCombobox } from "@/components/FormateurCombobox"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; @@ -402,11 +403,10 @@ export default function AdminSequences() {
- setFormData({ ...formData, formateur: e.target.value })} - placeholder="Nom du formateur (optionnel)" + onChange={(value) => setFormData({ ...formData, formateur: value })} + placeholder="Sélectionner un formateur (optionnel)" />
@@ -636,6 +636,7 @@ export default function AdminSequences() { Dates Lieu Public cible + Formateur Capacité Statut Actions @@ -662,6 +663,7 @@ export default function AdminSequences() { {getPublicCibleLabel(sequence.publicCible)} + {sequence.formateur || "-"} {sequence.nbInscrits || 0} / {sequence.capaciteMax} @@ -775,11 +777,10 @@ export default function AdminSequences() {
- setFormData({ ...formData, formateur: e.target.value })} - placeholder="Nom du formateur (optionnel)" + onChange={(value) => setFormData({ ...formData, formateur: value })} + placeholder="Sélectionner un formateur (optionnel)" />
diff --git a/drizzle/0017_dear_tana_nile.sql b/drizzle/0017_dear_tana_nile.sql new file mode 100644 index 0000000..1fb653d --- /dev/null +++ b/drizzle/0017_dear_tana_nile.sql @@ -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`) +); diff --git a/drizzle/meta/0017_snapshot.json b/drizzle/meta/0017_snapshot.json new file mode 100644 index 0000000..5ad1b73 --- /dev/null +++ b/drizzle/meta/0017_snapshot.json @@ -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": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 195796b..0306d07 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -120,6 +120,13 @@ "when": 1763562352026, "tag": "0016_chunky_landau", "breakpoints": true + }, + { + "idx": 17, + "version": "5", + "when": 1763563084443, + "tag": "0017_dear_tana_nile", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 5614a4d..22acd04 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -61,6 +61,20 @@ export const apprenants = mysqlTable("apprenants", { export type Apprenant = typeof apprenants.$inferSelect; 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 * Une séquence peut contenir jusqu'à 4 dates de formation diff --git a/server/db.ts b/server/db.ts index b51bf4d..5a70d40 100644 --- a/server/db.ts +++ b/server/db.ts @@ -5,6 +5,7 @@ import { users, formations, apprenants, + formateurs, sequences, datesFormation, inscriptions, @@ -18,10 +19,12 @@ import { InsertInscription, InsertFormation, InsertApprenant, + InsertFormateur, Sequence, DateFormation, Apprenant, Formation, + Formateur, EmailTemplate, InsertEmailTemplate } from "../drizzle/schema"; @@ -656,3 +659,37 @@ export async function updateEmailConfig(id: number, data: Partial; lieu: string; + formateur?: string; statut: 'confirmee' | 'liste_attente'; }): Promise { const isConfirmed = params.statut === 'confirmee'; @@ -88,6 +89,7 @@ export async function sendInscriptionConfirmation(params: {

Formation : ${params.formationNom}

Séquence : ${params.sequenceNom}

Lieu : ${params.lieu}

+ ${params.formateur ? `

Formateur : ${params.formateur}

` : ''}

Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :

${datesHTML}
diff --git a/server/routers.ts b/server/routers.ts index 4c0ee0c..92b1b48 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -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: router({ list: adminProcedure.query(async () => { @@ -330,6 +349,7 @@ export const appRouter = router({ ordre: d.ordre, })), lieu: inscriptionSequence.lieu, + formateur: inscriptionSequence.formateur || undefined, statut, }); } diff --git a/todo.md b/todo.md index 31d4303..847ae40 100644 --- a/todo.md +++ b/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 modification de séquence - [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