Checkpoint: Ajout du champ "Public cible" dans la fiche formation avec liste déroulante identique à celle de "Fonction" des apprenants (Directeur, Chef de service, Autre). Mise à jour du schéma de base de données, des procédures tRPC, du formulaire de gestion des formations avec affichage dans le tableau, et affichage du public cible sur la page d'inscription publique.

This commit is contained in:
Manus Sandbox
2025-11-13 10:35:54 -05:00
parent 4152b88c91
commit 5f4a049e05
8 changed files with 539 additions and 3 deletions

View File

@@ -22,6 +22,7 @@ export default function AdminFormations() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nom: "", nom: "",
description: "", description: "",
publicCible: "autre" as "directeur" | "chef_service" | "autre",
lienUnique: "", lienUnique: "",
actif: true, actif: true,
}); });
@@ -63,7 +64,7 @@ export default function AdminFormations() {
}); });
const resetForm = () => { const resetForm = () => {
setFormData({ nom: "", description: "", lienUnique: "", actif: true }); setFormData({ nom: "", description: "", publicCible: "autre" as "directeur" | "chef_service" | "autre", lienUnique: "", actif: true });
setEditingId(null); setEditingId(null);
}; };
@@ -71,6 +72,7 @@ export default function AdminFormations() {
setFormData({ setFormData({
nom: formation.nom, nom: formation.nom,
description: formation.description || "", description: formation.description || "",
publicCible: formation.publicCible || "autre",
lienUnique: formation.lienUnique, lienUnique: formation.lienUnique,
actif: formation.actif, actif: formation.actif,
}); });
@@ -167,6 +169,22 @@ export default function AdminFormations() {
rows={3} rows={3}
/> />
</div> </div>
<div className="space-y-2">
<Label htmlFor="publicCible">Public cible *</Label>
<Select
value={formData.publicCible}
onValueChange={(value: "directeur" | "chef_service" | "autre") => setFormData({ ...formData, publicCible: value })}
>
<SelectTrigger id="publicCible">
<SelectValue placeholder="Sélectionner le public cible" />
</SelectTrigger>
<SelectContent>
<SelectItem value="directeur">Directeur</SelectItem>
<SelectItem value="chef_service">Chef de service</SelectItem>
<SelectItem value="autre">Autre</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="lienUnique">Lien unique *</Label> <Label htmlFor="lienUnique">Lien unique *</Label>
<Input <Input
@@ -270,16 +288,20 @@ export default function AdminFormations() {
<TableRow> <TableRow>
<TableHead>Nom</TableHead> <TableHead>Nom</TableHead>
<TableHead>Description</TableHead> <TableHead>Description</TableHead>
<TableHead>Public cible</TableHead>
<TableHead>Lien d'inscription</TableHead> <TableHead>Lien d'inscription</TableHead>
<TableHead>Statut</TableHead> <TableHead>Statut</TableHead>
<TableHead className="text-right">Actions</TableHead> <TableHead className="text-right">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredFormations.map((formation) => ( {filteredFormations.map((formation) => {
const publicCibleLabel = formation.publicCible === "directeur" ? "Directeur" : formation.publicCible === "chef_service" ? "Chef de service" : "Autre";
return (
<TableRow key={formation.id}> <TableRow key={formation.id}>
<TableCell className="font-medium">{formation.nom}</TableCell> <TableCell className="font-medium">{formation.nom}</TableCell>
<TableCell className="max-w-xs truncate">{formation.description || "-"}</TableCell> <TableCell className="max-w-xs truncate">{formation.description || "-"}</TableCell>
<TableCell>{publicCibleLabel}</TableCell>
<TableCell> <TableCell>
<a <a
href={getInscriptionUrl(formation.lienUnique)} href={getInscriptionUrl(formation.lienUnique)}
@@ -319,7 +341,8 @@ export default function AdminFormations() {
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} );
})}
</TableBody> </TableBody>
</Table> </Table>
) : ( ) : (

View File

@@ -230,6 +230,10 @@ export default function Inscription() {
{formation.description} {formation.description}
</p> </p>
)} )}
<div className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-medium">
<Users className="w-4 h-4" />
Public cible : {formation.publicCible === "directeur" ? "Directeurs" : formation.publicCible === "chef_service" ? "Chefs de service" : "Autre"}
</div>
</div> </div>
{/* Sessions disponibles */} {/* Sessions disponibles */}

View File

@@ -0,0 +1 @@
ALTER TABLE `formations` ADD `publicCible` enum('directeur','chef_service','autre') NOT NULL;

View File

@@ -0,0 +1,492 @@
{
"version": "5",
"dialect": "mysql",
"id": "9dc05bf2-d92e-4abf-be1e-7357b388c9a5",
"prevId": "0352a8eb-52dd-4dee-814f-96378a8fce64",
"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": {}
},
"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
},
"publicCible": {
"name": "publicCible",
"type": "enum('directeur','chef_service','autre')",
"primaryKey": false,
"notNull": true,
"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": {}
},
"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
},
"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": true,
"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'"
},
"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": {
"users_openId_unique": {
"name": "users_openId_unique",
"columns": [
"openId"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -29,6 +29,13 @@
"when": 1762958719297, "when": 1762958719297,
"tag": "0003_steady_mystique", "tag": "0003_steady_mystique",
"breakpoints": true "breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1763047968048,
"tag": "0004_futuristic_malice",
"breakpoints": true
} }
] ]
} }

View File

@@ -32,6 +32,8 @@ export const formations = mysqlTable("formations", {
id: int("id").autoincrement().primaryKey(), id: int("id").autoincrement().primaryKey(),
nom: varchar("nom", { length: 255 }).notNull(), nom: varchar("nom", { length: 255 }).notNull(),
description: text("description"), description: text("description"),
/** Public cible de la formation (Directeur, Chef de service, Autre) */
publicCible: mysqlEnum("publicCible", ["directeur", "chef_service", "autre"]).notNull(),
/** Lien unique pour l'inscription à cette formation */ /** Lien unique pour l'inscription à cette formation */
lienUnique: varchar("lienUnique", { length: 100 }).notNull().unique(), lienUnique: varchar("lienUnique", { length: 100 }).notNull().unique(),
actif: boolean("actif").default(true).notNull(), actif: boolean("actif").default(true).notNull(),

View File

@@ -47,6 +47,7 @@ export const appRouter = router({
create: adminProcedure.input(z.object({ create: adminProcedure.input(z.object({
nom: z.string().min(1), nom: z.string().min(1),
description: z.string().optional(), description: z.string().optional(),
publicCible: z.enum(["directeur", "chef_service", "autre"]),
lienUnique: z.string().min(1), lienUnique: z.string().min(1),
actif: z.boolean().optional(), actif: z.boolean().optional(),
})).mutation(async ({ input }) => { })).mutation(async ({ input }) => {
@@ -58,6 +59,7 @@ export const appRouter = router({
id: z.number(), id: z.number(),
nom: z.string().min(1).optional(), nom: z.string().min(1).optional(),
description: z.string().optional(), description: z.string().optional(),
publicCible: z.enum(["directeur", "chef_service", "autre"]).optional(),
lienUnique: z.string().min(1).optional(), lienUnique: z.string().min(1).optional(),
actif: z.boolean().optional(), actif: z.boolean().optional(),
})).mutation(async ({ input }) => { })).mutation(async ({ input }) => {

View File

@@ -117,3 +117,8 @@
- [x] Corriger le décalage horaire persistant dans parseLocalDateTime (09:48 envoyé devient 10:48 en base) - [x] Corriger le décalage horaire persistant dans parseLocalDateTime (09:48 envoyé devient 10:48 en base)
- [x] Corriger l'erreur "toISOString is not a function" lors de la modification de séquences - [x] Corriger l'erreur "toISOString is not a function" lors de la modification de séquences
- [x] Ajouter le champ "Public cible" dans la fiche formation avec liste déroulante (Directeur, Chef de service, Autre)
- [x] Mettre à jour le schéma de base de données pour les formations
- [x] Mettre à jour les formulaires de gestion des formations
- [x] Afficher le public cible dans les listes et exports