equipe ui
This commit is contained in:
parent
7c3239996c
commit
bc02fdfb98
@ -497,17 +497,16 @@ export default function EmailSidebar({
|
|||||||
type="button"
|
type="button"
|
||||||
className="ml-1 text-gray-400 hover:text-gray-600 cursor-pointer flex items-center justify-center h-5 w-5"
|
className="ml-1 text-gray-400 hover:text-gray-600 cursor-pointer flex items-center justify-center h-5 w-5"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
aria-label="Account options"
|
aria-label="Account options"
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: '18px', lineHeight: 1 }}>⋮</span>
|
<span style={{ fontSize: '18px', lineHeight: 1 }}>⋮</span>
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={e => { e.stopPropagation(); onEditAccount(account); }}>
|
<DropdownMenuItem onClick={() => onEditAccount(account)}>
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={e => { e.stopPropagation(); onDeleteAccount(account); }}>
|
<DropdownMenuItem onClick={() => onDeleteAccount(account)}>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@ -53,6 +53,41 @@ interface GroupsTableProps {
|
|||||||
userRole?: string[];
|
userRole?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialogWrapper component to better handle dialog state
|
||||||
|
function DialogWrapper({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onAfterClose,
|
||||||
|
triggerComponent,
|
||||||
|
children,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onAfterClose?: () => void;
|
||||||
|
triggerComponent?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
|
onOpenChange(open);
|
||||||
|
|
||||||
|
if (!open && onAfterClose) {
|
||||||
|
// Use a longer timeout to ensure all animations are complete
|
||||||
|
setTimeout(onAfterClose, 300);
|
||||||
|
}
|
||||||
|
}, [onOpenChange, onAfterClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
|
{triggerComponent && <DialogTrigger asChild>{triggerComponent}</DialogTrigger>}
|
||||||
|
<DialogContent className={className}>
|
||||||
|
{children}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function GroupsTable({ userRole = [] }: GroupsTableProps) {
|
export function GroupsTable({ userRole = [] }: GroupsTableProps) {
|
||||||
const [groups, setGroups] = useState<Group[]>([]);
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@ -340,6 +375,22 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reset callbacks
|
||||||
|
const resetNewGroupForm = useCallback(() => {
|
||||||
|
setNewGroupName("");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetModifyGroupForm = useCallback(() => {
|
||||||
|
setSelectedGroup(null);
|
||||||
|
setModifiedGroupName("");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetMembersForm = useCallback(() => {
|
||||||
|
setSelectedGroup(null);
|
||||||
|
setGroupMembers([]);
|
||||||
|
setAvailableUsers([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (loading) return <div className="text-center p-4">Loading...</div>;
|
if (loading) return <div className="text-center p-4">Loading...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -352,44 +403,39 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="max-w-xs bg-white text-gray-900 border-gray-300"
|
className="max-w-xs bg-white text-gray-900 border-gray-300"
|
||||||
/>
|
/>
|
||||||
<Dialog open={newGroupDialog} onOpenChange={(open) => {
|
<DialogWrapper
|
||||||
setNewGroupDialog(open);
|
open={newGroupDialog}
|
||||||
if (!open) {
|
onOpenChange={setNewGroupDialog}
|
||||||
// Reset state when dialog closes
|
onAfterClose={resetNewGroupForm}
|
||||||
setTimeout(() => {
|
triggerComponent={
|
||||||
setNewGroupName("");
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
<Plus className="mr-2 h-4 w-4" /> Ajouter un groupe
|
<Plus className="mr-2 h-4 w-4" /> Ajouter un groupe
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent className="bg-white text-black border border-gray-300">
|
className="bg-white text-black border border-gray-300"
|
||||||
<DialogHeader>
|
>
|
||||||
<DialogTitle className="text-gray-900">Nouveau Groupe</DialogTitle>
|
<DialogHeader>
|
||||||
</DialogHeader>
|
<DialogTitle className="text-gray-900">Nouveau Groupe</DialogTitle>
|
||||||
<div className="space-y-4">
|
</DialogHeader>
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<Label htmlFor="groupName" className="text-gray-900">Nom du groupe</Label>
|
<div className="space-y-2">
|
||||||
<Input
|
<Label htmlFor="groupName" className="text-gray-900">Nom du groupe</Label>
|
||||||
id="groupName"
|
<Input
|
||||||
value={newGroupName}
|
id="groupName"
|
||||||
onChange={(e) => setNewGroupName(e.target.value)}
|
value={newGroupName}
|
||||||
placeholder="Entrez le nom du groupe"
|
onChange={(e) => setNewGroupName(e.target.value)}
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
placeholder="Entrez le nom du groupe"
|
||||||
/>
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
</div>
|
/>
|
||||||
<Button
|
|
||||||
onClick={handleCreateGroup}
|
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
|
||||||
>
|
|
||||||
Créer le groupe
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
<Button
|
||||||
</Dialog>
|
onClick={handleCreateGroup}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
||||||
|
>
|
||||||
|
Créer le groupe
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table className="bg-white border border-gray-300 rounded-md">
|
<Table className="bg-white border border-gray-300 rounded-md">
|
||||||
@ -455,130 +501,111 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Dialog
|
<DialogWrapper
|
||||||
open={modifyGroupDialog}
|
open={modifyGroupDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={setModifyGroupDialog}
|
||||||
setModifyGroupDialog(open);
|
onAfterClose={resetModifyGroupForm}
|
||||||
if (!open) {
|
className="bg-white text-black border border-gray-300"
|
||||||
// Reset state when dialog closes
|
|
||||||
setTimeout(() => {
|
|
||||||
setSelectedGroup(null);
|
|
||||||
setModifiedGroupName("");
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DialogContent className="bg-white text-black border border-gray-300">
|
<DialogHeader>
|
||||||
<DialogHeader>
|
<DialogTitle className="text-gray-900">Modifier le groupe</DialogTitle>
|
||||||
<DialogTitle className="text-gray-900">Modifier le groupe</DialogTitle>
|
</DialogHeader>
|
||||||
</DialogHeader>
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label htmlFor="modifiedGroupName" className="text-gray-900">Nom du groupe</Label>
|
||||||
<Label htmlFor="modifiedGroupName" className="text-gray-900">Nom du groupe</Label>
|
<Input
|
||||||
<Input
|
id="modifiedGroupName"
|
||||||
id="modifiedGroupName"
|
value={modifiedGroupName}
|
||||||
value={modifiedGroupName}
|
onChange={(e) => setModifiedGroupName(e.target.value)}
|
||||||
onChange={(e) => setModifiedGroupName(e.target.value)}
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
autoFocus
|
||||||
autoFocus
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={handleUpdateGroup}
|
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
|
||||||
>
|
|
||||||
Mettre à jour
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
<Button
|
||||||
</Dialog>
|
onClick={handleUpdateGroup}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
||||||
|
>
|
||||||
|
Mettre à jour
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogWrapper>
|
||||||
|
|
||||||
<Dialog
|
<DialogWrapper
|
||||||
open={manageMembersDialog}
|
open={manageMembersDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={setManageMembersDialog}
|
||||||
setManageMembersDialog(open);
|
onAfterClose={resetMembersForm}
|
||||||
if (!open) {
|
className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto bg-white text-black border border-gray-300"
|
||||||
// Reset state when dialog closes
|
|
||||||
setTimeout(() => {
|
|
||||||
setSelectedGroup(null);
|
|
||||||
setGroupMembers([]);
|
|
||||||
setAvailableUsers([]);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto bg-white text-black border border-gray-300">
|
<DialogHeader>
|
||||||
<DialogHeader>
|
<DialogTitle className="text-gray-900">
|
||||||
<DialogTitle className="text-gray-900">
|
Gérer les membres - {selectedGroup?.name}
|
||||||
Gérer les membres - {selectedGroup?.name}
|
</DialogTitle>
|
||||||
</DialogTitle>
|
</DialogHeader>
|
||||||
</DialogHeader>
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
<div>
|
||||||
<div>
|
<h3 className="text-lg font-medium mb-2 text-gray-900">Membres actuels</h3>
|
||||||
<h3 className="text-lg font-medium mb-2 text-gray-900">Membres actuels</h3>
|
{groupMembers.length === 0 ? (
|
||||||
{groupMembers.length === 0 ? (
|
<p className="text-gray-600">Ce groupe n'a pas de membres.</p>
|
||||||
<p className="text-gray-600">Ce groupe n'a pas de membres.</p>
|
) : (
|
||||||
) : (
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
{groupMembers.map((member) => (
|
||||||
{groupMembers.map((member) => (
|
<div key={member.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
||||||
<div key={member.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
<div className="text-gray-900">
|
||||||
<div className="text-gray-900">
|
<div className="font-medium">{member.username}</div>
|
||||||
<div className="font-medium">{member.username}</div>
|
<div className="text-sm text-gray-600">{member.firstName} {member.lastName} ({member.email})</div>
|
||||||
<div className="text-sm text-gray-600">{member.firstName} {member.lastName} ({member.email})</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleRemoveMember(member.id)}
|
|
||||||
className="text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200"
|
|
||||||
>
|
|
||||||
<Trash className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<Button
|
||||||
</div>
|
variant="outline"
|
||||||
)}
|
size="sm"
|
||||||
</div>
|
onClick={() => handleRemoveMember(member.id)}
|
||||||
|
className="text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200"
|
||||||
<div>
|
>
|
||||||
<h3 className="text-lg font-medium mb-2 text-gray-900">Ajouter des membres</h3>
|
<Trash className="h-4 w-4" />
|
||||||
<Input
|
</Button>
|
||||||
type="text"
|
</div>
|
||||||
placeholder="Rechercher un utilisateur..."
|
))}
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
className="mb-2 bg-white text-gray-900 border-gray-300"
|
|
||||||
/>
|
|
||||||
<div className="space-y-2 max-h-[250px] overflow-y-auto">
|
|
||||||
{availableUsers
|
|
||||||
.filter(user =>
|
|
||||||
!groupMembers.some(member => member.id === user.id) &&
|
|
||||||
(user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
user.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
user.lastName.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
||||||
)
|
|
||||||
.map((user) => (
|
|
||||||
<div key={user.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
<div className="font-medium">{user.username}</div>
|
|
||||||
<div className="text-sm text-gray-600">{user.firstName} {user.lastName} ({user.email})</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleAddMember(user.id)}
|
|
||||||
className="text-green-600 hover:bg-green-50 hover:text-green-700 border-green-200"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium mb-2 text-gray-900">Ajouter des membres</h3>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Rechercher un utilisateur..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="mb-2 bg-white text-gray-900 border-gray-300"
|
||||||
|
/>
|
||||||
|
<div className="space-y-2 max-h-[250px] overflow-y-auto">
|
||||||
|
{availableUsers
|
||||||
|
.filter(user =>
|
||||||
|
!groupMembers.some(member => member.id === user.id) &&
|
||||||
|
(user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.lastName.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||||
|
)
|
||||||
|
.map((user) => (
|
||||||
|
<div key={user.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
||||||
|
<div className="text-gray-900">
|
||||||
|
<div className="font-medium">{user.username}</div>
|
||||||
|
<div className="text-sm text-gray-600">{user.firstName} {user.lastName} ({user.email})</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleAddMember(user.id)}
|
||||||
|
className="text-green-600 hover:bg-green-50 hover:text-green-700 border-green-200"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</div>
|
||||||
</Dialog>
|
</DialogWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -41,9 +41,17 @@ const DialogContent = React.forwardRef<
|
|||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
onPointerDownOutside={(e) => {
|
||||||
|
// Allow clicks outside to propagate normally
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
))
|
))
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@ -65,6 +65,41 @@ interface UsersTableProps {
|
|||||||
|
|
||||||
const ITEMS_PER_PAGE = 10;
|
const ITEMS_PER_PAGE = 10;
|
||||||
|
|
||||||
|
// DialogWrapper component to better handle dialog state
|
||||||
|
function DialogWrapper({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onAfterClose,
|
||||||
|
triggerComponent,
|
||||||
|
children,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onAfterClose?: () => void;
|
||||||
|
triggerComponent?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
|
onOpenChange(open);
|
||||||
|
|
||||||
|
if (!open && onAfterClose) {
|
||||||
|
// Use a longer timeout to ensure all animations are complete
|
||||||
|
setTimeout(onAfterClose, 300);
|
||||||
|
}
|
||||||
|
}, [onOpenChange, onAfterClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
|
{triggerComponent && <DialogTrigger asChild>{triggerComponent}</DialogTrigger>}
|
||||||
|
<DialogContent className={className}>
|
||||||
|
{children}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function UsersTable({ userRole = [] }: UsersTableProps) {
|
export function UsersTable({ userRole = [] }: UsersTableProps) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
@ -403,6 +438,37 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
currentPage * ITEMS_PER_PAGE
|
currentPage * ITEMS_PER_PAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Define reset function callbacks
|
||||||
|
const resetNewUserForm = useCallback(() => {
|
||||||
|
setFormData({
|
||||||
|
username: "",
|
||||||
|
lastName: "",
|
||||||
|
firstName: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
roles: [],
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetEditForm = useCallback(() => {
|
||||||
|
setFormData({
|
||||||
|
username: "",
|
||||||
|
lastName: "",
|
||||||
|
firstName: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
roles: [],
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
setSelectedUser(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetRolesForm = useCallback(() => {
|
||||||
|
setFormData(prev => ({ ...prev, roles: [] }));
|
||||||
|
setSelectedUser(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!session) return null;
|
if (!session) return null;
|
||||||
if (loading) return <div className="text-center p-4">Loading...</div>;
|
if (loading) return <div className="text-center p-4">Loading...</div>;
|
||||||
|
|
||||||
@ -416,115 +482,103 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="max-w-sm bg-white text-gray-900 border-gray-300"
|
className="max-w-sm bg-white text-gray-900 border-gray-300"
|
||||||
/>
|
/>
|
||||||
<Dialog open={newUserDialog} onOpenChange={(open) => {
|
<DialogWrapper
|
||||||
setNewUserDialog(open);
|
open={newUserDialog}
|
||||||
if (!open) {
|
onOpenChange={setNewUserDialog}
|
||||||
setTimeout(() => {
|
onAfterClose={resetNewUserForm}
|
||||||
setFormData({
|
triggerComponent={
|
||||||
username: "",
|
|
||||||
lastName: "",
|
|
||||||
firstName: "",
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
roles: [],
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
Ajouter un utilisateur
|
Ajouter un utilisateur
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
}
|
||||||
<DialogContent className="max-h-[85vh] overflow-y-auto bg-white text-black border border-gray-300">
|
className="max-h-[85vh] overflow-y-auto bg-white text-black border border-gray-300"
|
||||||
<DialogHeader>
|
>
|
||||||
<DialogTitle className="text-gray-900">Nouvel Utilisateur</DialogTitle>
|
<DialogHeader>
|
||||||
</DialogHeader>
|
<DialogTitle className="text-gray-900">Nouvel Utilisateur</DialogTitle>
|
||||||
<form onSubmit={handleAddUser} className="space-y-3">
|
</DialogHeader>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<form onSubmit={handleAddUser} className="space-y-3">
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Label htmlFor="username" className="text-gray-900">Nom d'utilisateur</Label>
|
|
||||||
<Input
|
|
||||||
id="username"
|
|
||||||
value={formData.username}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value.trim() }))}
|
|
||||||
required
|
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="email" className="text-gray-900">Email</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value.trim() }))}
|
|
||||||
required
|
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="firstName" className="text-gray-900">Prénom</Label>
|
|
||||||
<Input
|
|
||||||
id="firstName"
|
|
||||||
value={formData.firstName}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value.trim() }))}
|
|
||||||
required
|
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="lastName" className="text-gray-900">Nom</Label>
|
|
||||||
<Input
|
|
||||||
id="lastName"
|
|
||||||
value={formData.lastName}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value.trim() }))}
|
|
||||||
required
|
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="password" className="text-gray-900">Mot de passe</Label>
|
<Label htmlFor="username" className="text-gray-900">Nom d'utilisateur</Label>
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="username"
|
||||||
type="password"
|
value={formData.username}
|
||||||
value={formData.password}
|
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value.trim() }))}
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
|
|
||||||
required
|
required
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-gray-900">Rôles</Label>
|
<Label htmlFor="email" className="text-gray-900">Email</Label>
|
||||||
<div className="grid grid-cols-2 gap-2 max-h-[120px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
<Input
|
||||||
{roles.map((role) => (
|
id="email"
|
||||||
<div key={role.id} className="flex items-center space-x-2">
|
type="email"
|
||||||
<Checkbox
|
value={formData.email}
|
||||||
id={`role-${role.id}`}
|
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value.trim() }))}
|
||||||
checked={formData.roles.includes(role.name)}
|
required
|
||||||
onCheckedChange={(checked) => {
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
setFormData(prev => ({
|
/>
|
||||||
...prev,
|
|
||||||
roles: checked
|
|
||||||
? [...prev.roles, role.name]
|
|
||||||
: prev.roles.filter(r => r !== role.name)
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
className="border-gray-500"
|
|
||||||
/>
|
|
||||||
<Label htmlFor={`role-${role.id}`} className="text-sm text-gray-900">{role.name}</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="w-full bg-blue-600 hover:bg-blue-700 text-white">Créer Utilisateur</Button>
|
</div>
|
||||||
</form>
|
<div className="grid grid-cols-2 gap-3">
|
||||||
</DialogContent>
|
<div className="space-y-2">
|
||||||
</Dialog>
|
<Label htmlFor="firstName" className="text-gray-900">Prénom</Label>
|
||||||
|
<Input
|
||||||
|
id="firstName"
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value.trim() }))}
|
||||||
|
required
|
||||||
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="lastName" className="text-gray-900">Nom</Label>
|
||||||
|
<Input
|
||||||
|
id="lastName"
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value.trim() }))}
|
||||||
|
required
|
||||||
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password" className="text-gray-900">Mot de passe</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
|
||||||
|
required
|
||||||
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-gray-900">Rôles</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2 max-h-[120px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
||||||
|
{roles.map((role) => (
|
||||||
|
<div key={role.id} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`role-${role.id}`}
|
||||||
|
checked={formData.roles.includes(role.name)}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
roles: checked
|
||||||
|
? [...prev.roles, role.name]
|
||||||
|
: prev.roles.filter(r => r !== role.name)
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
className="border-gray-500"
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`role-${role.id}`} className="text-sm text-gray-900">{role.name}</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full bg-blue-600 hover:bg-blue-700 text-white">Créer Utilisateur</Button>
|
||||||
|
</form>
|
||||||
|
</DialogWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table className="bg-white border border-gray-300 rounded-md">
|
<Table className="bg-white border border-gray-300 rounded-md">
|
||||||
@ -564,10 +618,6 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span className="sr-only">Open menu</span>
|
<span className="sr-only">Open menu</span>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
@ -628,147 +678,129 @@ export function UsersTable({ userRole = [] }: UsersTableProps) {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Dialog open={editUserDialog} onOpenChange={(open) => {
|
<DialogWrapper
|
||||||
if (!open) {
|
open={editUserDialog}
|
||||||
setTimeout(() => {
|
onOpenChange={setEditUserDialog}
|
||||||
setFormData({
|
onAfterClose={resetEditForm}
|
||||||
username: "",
|
className="bg-white text-black border border-gray-300"
|
||||||
lastName: "",
|
>
|
||||||
firstName: "",
|
<DialogHeader>
|
||||||
email: "",
|
<DialogTitle className="text-gray-900">Modifier l'utilisateur</DialogTitle>
|
||||||
password: "",
|
</DialogHeader>
|
||||||
roles: [],
|
<form onSubmit={handleUpdateUser} className="space-y-4">
|
||||||
enabled: true,
|
<div className="space-y-2">
|
||||||
});
|
<Label htmlFor="edit-firstName" className="text-gray-900">Prénom</Label>
|
||||||
setSelectedUser(null);
|
<Input
|
||||||
}, 100);
|
id="edit-firstName"
|
||||||
}
|
value={formData.firstName}
|
||||||
setEditUserDialog(open);
|
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value }))}
|
||||||
}}>
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
<DialogContent className="bg-white text-black border border-gray-300">
|
autoFocus
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle className="text-gray-900">Modifier l'utilisateur</DialogTitle>
|
</div>
|
||||||
</DialogHeader>
|
<div className="space-y-2">
|
||||||
<form onSubmit={handleUpdateUser} className="space-y-4">
|
<Label htmlFor="edit-lastName" className="text-gray-900">Nom</Label>
|
||||||
<div className="space-y-2">
|
<Input
|
||||||
<Label htmlFor="edit-firstName" className="text-gray-900">Prénom</Label>
|
id="edit-lastName"
|
||||||
<Input
|
value={formData.lastName}
|
||||||
id="edit-firstName"
|
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value }))}
|
||||||
value={formData.firstName}
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value }))}
|
/>
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
</div>
|
||||||
autoFocus
|
<div className="space-y-2">
|
||||||
/>
|
<Label htmlFor="edit-email" className="text-gray-900">Email</Label>
|
||||||
</div>
|
<Input
|
||||||
<div className="space-y-2">
|
id="edit-email"
|
||||||
<Label htmlFor="edit-lastName" className="text-gray-900">Nom</Label>
|
type="email"
|
||||||
<Input
|
value={formData.email}
|
||||||
id="edit-lastName"
|
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||||||
value={formData.lastName}
|
className="bg-white text-gray-900 border-gray-300"
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value }))}
|
/>
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
</div>
|
||||||
/>
|
<div className="flex justify-end space-x-2">
|
||||||
</div>
|
<Button
|
||||||
<div className="space-y-2">
|
type="button"
|
||||||
<Label htmlFor="edit-email" className="text-gray-900">Email</Label>
|
variant="outline"
|
||||||
<Input
|
onClick={() => {
|
||||||
id="edit-email"
|
setEditUserDialog(false);
|
||||||
type="email"
|
setSelectedUser(null);
|
||||||
value={formData.email}
|
setFormData({
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
username: "",
|
||||||
className="bg-white text-gray-900 border-gray-300"
|
lastName: "",
|
||||||
/>
|
firstName: "",
|
||||||
</div>
|
email: "",
|
||||||
<div className="flex justify-end space-x-2">
|
password: "",
|
||||||
<Button
|
roles: [],
|
||||||
type="button"
|
enabled: true,
|
||||||
variant="outline"
|
});
|
||||||
onClick={() => {
|
}}
|
||||||
setEditUserDialog(false);
|
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
||||||
setSelectedUser(null);
|
>
|
||||||
setFormData({
|
Annuler
|
||||||
username: "",
|
</Button>
|
||||||
lastName: "",
|
<Button type="submit" className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
firstName: "",
|
Modifier
|
||||||
email: "",
|
</Button>
|
||||||
password: "",
|
</div>
|
||||||
roles: [],
|
</form>
|
||||||
enabled: true,
|
</DialogWrapper>
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
Annuler
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" className="bg-blue-600 hover:bg-blue-700 text-white">
|
|
||||||
Modifier
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<Dialog open={manageRolesDialog} onOpenChange={(open) => {
|
<DialogWrapper
|
||||||
if (!open) {
|
open={manageRolesDialog}
|
||||||
setTimeout(() => {
|
onOpenChange={setManageRolesDialog}
|
||||||
setFormData(prev => ({ ...prev, roles: [] }));
|
onAfterClose={resetRolesForm}
|
||||||
setSelectedUser(null);
|
className="bg-white text-black border border-gray-300"
|
||||||
}, 100);
|
>
|
||||||
}
|
<DialogHeader>
|
||||||
setManageRolesDialog(open);
|
<DialogTitle className="text-gray-900">Gérer les rôles pour {selectedUser?.username}</DialogTitle>
|
||||||
}}>
|
</DialogHeader>
|
||||||
<DialogContent className="bg-white text-black border border-gray-300">
|
<div className="space-y-4">
|
||||||
<DialogHeader>
|
<div className="space-y-2">
|
||||||
<DialogTitle className="text-gray-900">Gérer les rôles pour {selectedUser?.username}</DialogTitle>
|
<Label className="text-gray-900">Rôles disponibles</Label>
|
||||||
</DialogHeader>
|
<div className="grid grid-cols-2 gap-2 max-h-[200px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
||||||
<div className="space-y-4">
|
{roles.map((role) => (
|
||||||
<div className="space-y-2">
|
<div key={role.id} className="flex items-center space-x-2 p-2 rounded-md hover:bg-gray-100">
|
||||||
<Label className="text-gray-900">Rôles disponibles</Label>
|
<Checkbox
|
||||||
<div className="grid grid-cols-2 gap-2 max-h-[200px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
id={`manage-role-${role.id}`}
|
||||||
{roles.map((role) => (
|
checked={formData.roles.includes(role.name)}
|
||||||
<div key={role.id} className="flex items-center space-x-2 p-2 rounded-md hover:bg-gray-100">
|
onCheckedChange={(checked) => {
|
||||||
<Checkbox
|
setFormData(prev => ({
|
||||||
id={`manage-role-${role.id}`}
|
...prev,
|
||||||
checked={formData.roles.includes(role.name)}
|
roles: checked
|
||||||
onCheckedChange={(checked) => {
|
? [...prev.roles, role.name]
|
||||||
setFormData(prev => ({
|
: prev.roles.filter(r => r !== role.name)
|
||||||
...prev,
|
}));
|
||||||
roles: checked
|
}}
|
||||||
? [...prev.roles, role.name]
|
className="border-gray-500"
|
||||||
: prev.roles.filter(r => r !== role.name)
|
/>
|
||||||
}));
|
<Label
|
||||||
}}
|
htmlFor={`manage-role-${role.id}`}
|
||||||
className="border-gray-500"
|
className="text-sm text-gray-900"
|
||||||
/>
|
>
|
||||||
<Label
|
{role.name}
|
||||||
htmlFor={`manage-role-${role.id}`}
|
</Label>
|
||||||
className="text-sm text-gray-900"
|
</div>
|
||||||
>
|
))}
|
||||||
{role.name}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end space-x-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setManageRolesDialog(false);
|
|
||||||
setSelectedUser(null);
|
|
||||||
setFormData(prev => ({ ...prev, roles: [] }));
|
|
||||||
}}
|
|
||||||
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
Annuler
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleUpdateRoles} className="bg-blue-600 hover:bg-blue-700 text-white">
|
|
||||||
Mettre à jour les rôles
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
<div className="flex justify-end space-x-2">
|
||||||
</Dialog>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setManageRolesDialog(false);
|
||||||
|
setSelectedUser(null);
|
||||||
|
setFormData(prev => ({ ...prev, roles: [] }));
|
||||||
|
}}
|
||||||
|
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleUpdateRoles} className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
|
Mettre à jour les rôles
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user