Checkpoint: Affichage unifié des formateurs dans la page Utilisateurs

## Modifications effectuées :

### Backend (server/routers.ts)
- Ajout de la procédure `users.listWithFormateurs` qui retourne :
  * Tous les utilisateurs
  * Tous les formateurs avec leur statut de compte (hasAccount, user)
- Cette procédure combine les données des tables `users` et `formateurs`

### Frontend (client/src/pages/AdminUsers.tsx)
- Remplacement de `users.list` par `users.listWithFormateurs`
- Ajout d'une section dédiée "Formateurs sans compte utilisateur" affichée en haut de la page
- Cette section liste tous les formateurs qui n'ont pas encore de compte utilisateur
- Bouton "Créer un compte" pour chaque formateur qui :
  * Pré-remplit le formulaire avec les données du formateur (nom, email)
  * Lie automatiquement le `formateurId`
  * Définit le rôle sur "formateur"

## Résultat
Les formateurs existants dans la table `formateurs` sont maintenant visibles dans la page Utilisateurs. L'administrateur peut facilement créer un compte pour chaque formateur en un clic, ce qui lie automatiquement le compte au formateur existant.
This commit is contained in:
Manus
2026-01-08 03:44:41 -05:00
parent 5367c7ea7a
commit 6184dff68f
3 changed files with 82 additions and 2 deletions

View File

@@ -58,8 +58,9 @@ export default function AdminUsers() {
formateurId: undefined as number | undefined, formateurId: undefined as number | undefined,
}); });
const { data: users = [], refetch } = trpc.users.list.useQuery(); const { data: usersData, refetch } = trpc.users.listWithFormateurs.useQuery();
const { data: formateurs = [] } = trpc.formateurs.list.useQuery(); const users = usersData?.users || [];
const formateurs = usersData?.formateurs || [];
const createMutation = trpc.users.create.useMutation({ const createMutation = trpc.users.create.useMutation({
onSuccess: () => { onSuccess: () => {
@@ -295,6 +296,46 @@ export default function AdminUsers() {
</div> </div>
</div> </div>
{/* Formateurs sans compte */}
{formateurs.filter(f => !f.hasAccount).length > 0 && (
<div className="mb-6 p-4 border border-orange-200 bg-orange-50 rounded-lg">
<h3 className="font-semibold text-orange-800 mb-2">
Formateurs sans compte utilisateur ({formateurs.filter(f => !f.hasAccount).length})
</h3>
<p className="text-sm text-orange-700 mb-4">
Ces formateurs existent dans le système mais n'ont pas de compte utilisateur. Créez un compte pour leur permettre de se connecter.
</p>
<div className="space-y-2">
{formateurs.filter(f => !f.hasAccount).map((formateur) => (
<div key={formateur.id} className="flex items-center justify-between bg-white p-3 rounded border">
<div>
<p className="font-medium">{formateur.nom}</p>
<p className="text-sm text-muted-foreground">{formateur.email || "Pas d'email"}</p>
</div>
<Button
size="sm"
onClick={() => {
setCreateForm({
openId: `formateur_${formateur.id}_${Date.now()}`,
name: formateur.nom,
email: formateur.email || "",
username: "",
password: "",
role: "formateur",
formateurId: formateur.id,
});
setCreateDialogOpen(true);
}}
>
<Plus className="h-4 w-4 mr-2" />
Créer un compte
</Button>
</div>
))}
</div>
</div>
)}
{/* Compteur de résultats */} {/* Compteur de résultats */}
<div className="mb-4"> <div className="mb-4">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View File

@@ -894,6 +894,38 @@ export const appRouter = router({
return db.getAllUsers(); return db.getAllUsers();
}), }),
listWithFormateurs: adminProcedure.query(async () => {
const dbInstance = await import("./db").then(m => m.getDb());
if (!dbInstance) throw new Error("Base de données non disponible");
const { formateurs, users } = await import("../drizzle/schema");
const { eq } = await import("drizzle-orm");
// Récupérer tous les formateurs
const allFormateurs = await dbInstance.select().from(formateurs);
// Récupérer tous les utilisateurs
const allUsers = await dbInstance.select().from(users);
// Créer une map des formateurs liés à des comptes
const formateurIdToUser = new Map();
allUsers.forEach(user => {
if (user.formateurId) {
formateurIdToUser.set(user.formateurId, user);
}
});
// Combiner les données
return {
users: allUsers,
formateurs: allFormateurs.map(formateur => ({
...formateur,
hasAccount: formateurIdToUser.has(formateur.id),
user: formateurIdToUser.get(formateur.id) || null,
})),
};
}),
getById: adminProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => { getById: adminProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
return db.getUserById(input.id); return db.getUserById(input.id);
}), }),

View File

@@ -486,3 +486,10 @@
- [x] Retirer le bouton de création de formateur de la page Formateurs - [x] Retirer le bouton de création de formateur de la page Formateurs
- [x] Supprimer le dialogue de création et les mutations associées - [x] Supprimer le dialogue de création et les mutations associées
- [x] Ajouter un message explicatif indiquant que la création se fait via la page Utilisateurs - [x] Ajouter un message explicatif indiquant que la création se fait via la page Utilisateurs
## Liaison formateurs existants avec utilisateurs
- [x] Analyser les formateurs existants dans la table formateurs
- [x] Créer une procédure listWithFormateurs pour afficher tous les formateurs
- [x] Afficher les formateurs sans compte dans une section dédiée
- [x] Ajouter un bouton "Créer un compte" qui pré-remplit le formulaire
- [x] Lier automatiquement le formateurId lors de la création