Checkpoint: Ajout de la fonctionnalité de création d'utilisateurs : ajout de la fonction createUser dans db.ts, création de la procédure tRPC users.create, ajout du bouton "Ajouter un utilisateur" dans la page AdminUsers avec icône Plus, création du dialogue de création avec formulaire complet (OpenID, nom, email, rôle) et gestion des états de création avec validation et messages de succès/erreur

This commit is contained in:
Manus Sandbox
2025-11-17 16:10:16 -05:00
parent 8cf6a9f6c3
commit c9f6f8f068
4 changed files with 154 additions and 6 deletions

View File

@@ -28,15 +28,22 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { toast } from "sonner"; import { toast } from "sonner";
import { Pencil, Trash2, UserCheck, UserX, Search } from "lucide-react"; import { Pencil, Trash2, UserCheck, UserX, Search, Plus } from "lucide-react";
export default function AdminUsers() { export default function AdminUsers() {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [filterRole, setFilterRole] = useState<string>("all"); const [filterRole, setFilterRole] = useState<string>("all");
const [filterStatus, setFilterStatus] = useState<string>("all"); const [filterStatus, setFilterStatus] = useState<string>("all");
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState<any>(null); const [selectedUser, setSelectedUser] = useState<any>(null);
const [createForm, setCreateForm] = useState({
openId: "",
name: "",
email: "",
role: "user" as "user" | "admin",
});
const [editForm, setEditForm] = useState({ const [editForm, setEditForm] = useState({
name: "", name: "",
email: "", email: "",
@@ -44,6 +51,24 @@ export default function AdminUsers() {
}); });
const { data: users = [], refetch } = trpc.users.list.useQuery(); const { data: users = [], refetch } = trpc.users.list.useQuery();
const createMutation = trpc.users.create.useMutation({
onSuccess: () => {
toast.success("Utilisateur créé avec succès");
refetch();
setCreateDialogOpen(false);
setCreateForm({
openId: "",
name: "",
email: "",
role: "user",
});
},
onError: (error) => {
toast.error(`Erreur : ${error.message}`);
},
});
const updateMutation = trpc.users.update.useMutation({ const updateMutation = trpc.users.update.useMutation({
onSuccess: () => { onSuccess: () => {
toast.success("Utilisateur modifié avec succès"); toast.success("Utilisateur modifié avec succès");
@@ -76,6 +101,25 @@ export default function AdminUsers() {
}, },
}); });
const handleCreate = () => {
setCreateDialogOpen(true);
};
const handleSaveCreate = () => {
if (!createForm.openId) {
toast.error("L'identifiant OpenID est obligatoire");
return;
}
createMutation.mutate({
openId: createForm.openId,
name: createForm.name || undefined,
email: createForm.email || undefined,
role: createForm.role,
isActive: true,
});
};
const handleEdit = (user: any) => { const handleEdit = (user: any) => {
setSelectedUser(user); setSelectedUser(user);
setEditForm({ setEditForm({
@@ -142,12 +186,18 @@ export default function AdminUsers() {
return ( return (
<div className="container mx-auto py-8"> <div className="container mx-auto py-8">
<div className="mb-6"> <div className="mb-6 flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold mb-2">Gestion des Utilisateurs</h1> <h1 className="text-3xl font-bold mb-2">Gestion des Utilisateurs</h1>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Gérez les comptes utilisateurs de l'application Gérez les comptes utilisateurs de l'application
</p> </p>
</div> </div>
<Button onClick={handleCreate}>
<Plus className="h-4 w-4 mr-2" />
Ajouter un utilisateur
</Button>
</div>
{/* Filtres */} {/* Filtres */}
<div className="mb-6 flex flex-wrap gap-4"> <div className="mb-6 flex flex-wrap gap-4">
@@ -280,6 +330,78 @@ export default function AdminUsers() {
</Table> </Table>
</div> </div>
{/* Dialog de création */}
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Créer un nouvel utilisateur</DialogTitle>
<DialogDescription>
Ajoutez un nouvel utilisateur à l'application
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="createOpenId">Identifiant OpenID *</Label>
<Input
id="createOpenId"
value={createForm.openId}
onChange={(e) => setCreateForm({ ...createForm, openId: e.target.value })}
placeholder="Identifiant unique de l'utilisateur"
/>
</div>
<div>
<Label htmlFor="createName">Nom</Label>
<Input
id="createName"
value={createForm.name}
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
placeholder="Nom de l'utilisateur"
/>
</div>
<div>
<Label htmlFor="createEmail">Email</Label>
<Input
id="createEmail"
type="email"
value={createForm.email}
onChange={(e) => setCreateForm({ ...createForm, email: e.target.value })}
placeholder="email@exemple.com"
/>
</div>
<div>
<Label htmlFor="createRole">Rôle</Label>
<Select
value={createForm.role}
onValueChange={(value: "user" | "admin") =>
setCreateForm({ ...createForm, role: value })
}
>
<SelectTrigger id="createRole">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="user">Utilisateur</SelectItem>
<SelectItem value="admin">Administrateur</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setCreateDialogOpen(false)}>
Annuler
</Button>
<Button onClick={handleSaveCreate} disabled={createMutation.isPending}>
{createMutation.isPending ? "Création..." : "Créer"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Dialog de modification */} {/* Dialog de modification */}
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}> <Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogContent> <DialogContent>

View File

@@ -376,6 +376,14 @@ export async function deleteInscription(id: number) {
// ==================== GESTION DES UTILISATEURS ==================== // ==================== GESTION DES UTILISATEURS ====================
export async function createUser(data: InsertUser) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db.insert(users).values(data);
return result;
}
export async function getAllUsers() { export async function getAllUsers() {
const db = await getDb(); const db = await getDb();
if (!db) return []; if (!db) return [];

View File

@@ -548,6 +548,17 @@ export const appRouter = router({
return db.getUserById(input.id); return db.getUserById(input.id);
}), }),
create: adminProcedure.input(z.object({
openId: z.string().min(1),
name: z.string().optional(),
email: z.string().email().optional(),
role: z.enum(["user", "admin"]).default("user"),
isActive: z.boolean().default(true),
})).mutation(async ({ input }) => {
await db.createUser(input);
return { success: true };
}),
update: adminProcedure.input(z.object({ update: adminProcedure.input(z.object({
id: z.number(), id: z.number(),
name: z.string().optional(), name: z.string().optional(),

View File

@@ -188,3 +188,10 @@
- [x] Ajouter la gestion des rôles (admin, user) - [x] Ajouter la gestion des rôles (admin, user)
- [x] Intégrer la page dans le menu de navigation - [x] Intégrer la page dans le menu de navigation
- [x] Tester toutes les fonctionnalités de gestion des utilisateurs - [x] Tester toutes les fonctionnalités de gestion des utilisateurs
## Ajout de la fonctionnalité de création d'utilisateurs
- [x] Ajouter une procédure tRPC pour créer un nouvel utilisateur
- [x] Ajouter un bouton "Ajouter un utilisateur" dans la page AdminUsers
- [x] Créer un dialogue de création avec formulaire (nom, email, rôle)
- [x] Tester la création d'un nouvel utilisateur