Checkpoint: Ajout du champ "Formateur" dans les séquences de formation :

 Base de données : colonne "formateur" ajoutée à la table sequences (VARCHAR 255, nullable)

 Backend : procédures tRPC mises à jour
- sequences.create : champ formateur ajouté (optionnel)
- sequences.update : champ formateur ajouté (optionnel)

 Frontend : formulaires mis à jour
- Formulaire de création : champ "Formateur" ajouté avec placeholder
- Formulaire de modification : champ "Formateur" ajouté avec placeholder
- Le champ est non obligatoire comme demandé

Le champ formateur permet maintenant de spécifier le nom du formateur pour chaque séquence de formation lors de la création ou de la modification.
This commit is contained in:
Manus Sandbox
2025-11-19 09:30:51 -05:00
parent b9907f9cca
commit 703d0e4961
8 changed files with 834 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
{
"query": "ALTER TABLE sequences ADD COLUMN formateur VARCHAR(255) NULL AFTER publicCible;",
"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 ALTER TABLE sequences ADD COLUMN formateur VARCHAR(255) NULL AFTER publicCible;",
"rows": [],
"messages": [],
"stdout": "",
"stderr": "",
"execution_time_ms": 495
}

View File

@@ -35,6 +35,7 @@ export default function AdminSequences() {
nom: "", nom: "",
lieu: "", lieu: "",
publicCible: "autre" as "directeur" | "chef_service" | "tous" | "autre", publicCible: "autre" as "directeur" | "chef_service" | "tous" | "autre",
formateur: "",
capaciteMax: "12", capaciteMax: "12",
dateBlocage: "", dateBlocage: "",
statut: "ouverte" as "ouverte" | "bloquee" | "terminee", statut: "ouverte" as "ouverte" | "bloquee" | "terminee",
@@ -88,6 +89,7 @@ export default function AdminSequences() {
nom: "", nom: "",
lieu: "", lieu: "",
publicCible: "autre", publicCible: "autre",
formateur: "",
capaciteMax: "12", capaciteMax: "12",
dateBlocage: "", dateBlocage: "",
statut: "ouverte", statut: "ouverte",
@@ -139,6 +141,7 @@ export default function AdminSequences() {
nom: sequence.nom || "", nom: sequence.nom || "",
lieu: sequence.lieu || "", lieu: sequence.lieu || "",
publicCible: sequence.publicCible || "autre", publicCible: sequence.publicCible || "autre",
formateur: sequence.formateur || "",
capaciteMax: sequence.capaciteMax?.toString() || "12", capaciteMax: sequence.capaciteMax?.toString() || "12",
dateBlocage: safeFormatDate(sequence.dateBlocage), dateBlocage: safeFormatDate(sequence.dateBlocage),
statut: sequence.statut || "ouverte", statut: sequence.statut || "ouverte",
@@ -164,6 +167,7 @@ export default function AdminSequences() {
nom: formData.nom, nom: formData.nom,
lieu: formData.lieu, lieu: formData.lieu,
publicCible: formData.publicCible, publicCible: formData.publicCible,
formateur: formData.formateur || undefined,
capaciteMax: parseInt(formData.capaciteMax), capaciteMax: parseInt(formData.capaciteMax),
dateBlocage: formData.dateBlocage, dateBlocage: formData.dateBlocage,
statut: formData.statut, statut: formData.statut,
@@ -393,6 +397,18 @@ export default function AdminSequences() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="formateur">Formateur</Label>
<Input
id="formateur"
value={formData.formateur}
onChange={(e) => setFormData({ ...formData, formateur: e.target.value })}
placeholder="Nom du formateur (optionnel)"
/>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="capaciteMax">Capacité maximale *</Label> <Label htmlFor="capaciteMax">Capacité maximale *</Label>
@@ -757,6 +773,16 @@ export default function AdminSequences() {
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-formateur">Formateur</Label>
<Input
id="edit-formateur"
value={formData.formateur}
onChange={(e) => setFormData({ ...formData, formateur: e.target.value })}
placeholder="Nom du formateur (optionnel)"
/>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="edit-capaciteMax">Capacité maximale *</Label> <Label htmlFor="edit-capaciteMax">Capacité maximale *</Label>
<Input <Input

View File

@@ -0,0 +1 @@
ALTER TABLE `sequences` ADD `formateur` varchar(255);

View File

@@ -0,0 +1,779 @@
{
"version": "5",
"dialect": "mysql",
"id": "860496d9-3739-4694-b45d-725a447f3eee",
"prevId": "227aca0a-a830-4a26-9bd7-edca209e040b",
"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": {}
},
"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": {}
}
}

View File

@@ -113,6 +113,13 @@
"when": 1763561100930, "when": 1763561100930,
"tag": "0015_brainy_terrax", "tag": "0015_brainy_terrax",
"breakpoints": true "breakpoints": true
},
{
"idx": 16,
"version": "5",
"when": 1763562352026,
"tag": "0016_chunky_landau",
"breakpoints": true
} }
] ]
} }

View File

@@ -73,6 +73,8 @@ export const sequences = mysqlTable("sequences", {
lieu: varchar("lieu", { length: 500 }).notNull(), lieu: varchar("lieu", { length: 500 }).notNull(),
/** Public cible de la séquence (Directeur, Chef de service, Tous, Autre) */ /** Public cible de la séquence (Directeur, Chef de service, Tous, Autre) */
publicCible: mysqlEnum("publicCible", ["directeur", "chef_service", "tous", "autre"]).notNull(), publicCible: mysqlEnum("publicCible", ["directeur", "chef_service", "tous", "autre"]).notNull(),
/** Nom du formateur (optionnel) */
formateur: varchar("formateur", { length: 255 }),
/** Capacité maximale (12 par défaut) */ /** Capacité maximale (12 par défaut) */
capaciteMax: int("capaciteMax").default(12).notNull(), capaciteMax: int("capaciteMax").default(12).notNull(),
/** Date de blocage des inscriptions (J-15 avant la première date) */ /** Date de blocage des inscriptions (J-15 avant la première date) */

View File

@@ -156,6 +156,7 @@ export const appRouter = router({
nom: z.string().min(1), nom: z.string().min(1),
lieu: z.string().min(1), lieu: z.string().min(1),
publicCible: z.enum(["directeur", "chef_service", "tous", "autre"]), publicCible: z.enum(["directeur", "chef_service", "tous", "autre"]),
formateur: z.string().optional(),
capaciteMax: z.number().default(12), capaciteMax: z.number().default(12),
dateBlocage: z.string(), dateBlocage: z.string(),
statut: z.enum(["ouverte", "bloquee", "terminee"]).optional(), statut: z.enum(["ouverte", "bloquee", "terminee"]).optional(),
@@ -195,6 +196,7 @@ export const appRouter = router({
nom: z.string().min(1), nom: z.string().min(1),
lieu: z.string().min(1), lieu: z.string().min(1),
publicCible: z.enum(["directeur", "chef_service", "tous", "autre"]), publicCible: z.enum(["directeur", "chef_service", "tous", "autre"]),
formateur: z.string().optional(),
capaciteMax: z.number(), capaciteMax: z.number(),
dateBlocage: z.string(), dateBlocage: z.string(),
statut: z.enum(["ouverte", "bloquee", "terminee"]), statut: z.enum(["ouverte", "bloquee", "terminee"]),

View File

@@ -262,3 +262,11 @@
- [x] Ajouter le champ "Public cible" dans le dialogue de modification des séquences - [x] Ajouter le champ "Public cible" dans le dialogue de modification des séquences
- [x] Afficher "nombre d'inscrits / capacité" dans la colonne capacité du tableau des séquences - [x] Afficher "nombre d'inscrits / capacité" dans la colonne capacité du tableau des séquences
## Ajout du champ Formateur dans les séquences
- [x] Ajouter la colonne "formateur" dans la table sequences (texte nullable)
- [x] Mettre à jour les procédures tRPC create et update pour inclure le champ formateur
- [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