refactor(admin): replace parameterized routes with modal-based editing for users and roles

This commit is contained in:
2026-04-22 16:15:43 -04:00
parent 16edecdc56
commit f54b2640ad
10 changed files with 643 additions and 680 deletions
+34 -55
View File
@@ -1,32 +1,29 @@
'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { Card, Table, Badge, StatusBadge, Button, UserAvatar } from '@zen/core/shared/components';
import { PencilEdit01Icon } from '@zen/core/shared/icons';
import { useToast } from '@zen/core/toast';
import AdminHeader from '../components/AdminHeader.js';
import UserEditModal from '../components/UserEditModal.client.js';
const UsersPageClient = () => {
const router = useRouter();
const toast = useToast();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [roleColorMap, setRoleColorMap] = useState({});
// Pagination state
const [editingUserId, setEditingUserId] = useState(null);
const [pagination, setPagination] = useState({
page: 1,
limit: 20,
total: 0,
totalPages: 0
totalPages: 0,
});
// Sort state
const [sortBy, setSortBy] = useState('created_at');
const [sortOrder, setSortOrder] = useState('desc');
// Table columns configuration
const columns = [
{
key: 'name',
@@ -43,8 +40,8 @@ const UsersPageClient = () => {
),
skeleton: {
height: 'h-4', width: '60%',
secondary: { height: 'h-3', width: '40%' }
}
secondary: { height: 'h-3', width: '40%' },
},
},
{
key: 'role',
@@ -55,14 +52,14 @@ const UsersPageClient = () => {
{user.role}
</Badge>
),
skeleton: { height: 'h-6', width: '80px', className: 'rounded-full' }
skeleton: { height: 'h-6', width: '80px', className: 'rounded-full' },
},
{
key: 'email_verified',
label: 'Statut',
sortable: true,
render: (user) => <StatusBadge status={user.email_verified ? 'verified' : 'unverified'} />,
skeleton: { height: 'h-6', width: '90px', className: 'rounded-full' }
skeleton: { height: 'h-6', width: '90px', className: 'rounded-full' },
},
{
key: 'created_at',
@@ -73,7 +70,7 @@ const UsersPageClient = () => {
{formatDate(user.created_at)}
</span>
),
skeleton: { height: 'h-4', width: '70%' }
skeleton: { height: 'h-4', width: '70%' },
},
{
key: 'actions',
@@ -84,35 +81,28 @@ const UsersPageClient = () => {
<Button
variant="secondary"
size="sm"
onClick={() => router.push(`/admin/users/edit/${user.id}`)}
onClick={() => setEditingUserId(user.id)}
icon={<PencilEdit01Icon className="w-4 h-4" />}
>
Modifier
</Button>
),
skeleton: { height: 'h-8', width: '80px', className: 'rounded-lg' }
}
skeleton: { height: 'h-8', width: '80px', className: 'rounded-lg' },
},
];
// Fetch users function
const fetchUsers = async () => {
setLoading(true);
try {
const searchParams = new URLSearchParams({
page: pagination.page.toString(),
limit: pagination.limit.toString(),
sortBy,
sortOrder
sortOrder,
});
const response = await fetch(`/zen/api/users?${searchParams}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const response = await fetch(`/zen/api/users?${searchParams}`, { credentials: 'include' });
if (!response.ok) throw new Error(`Error: ${response.status}`);
const data = await response.json();
setUsers(data.users);
@@ -120,11 +110,10 @@ const UsersPageClient = () => {
...prev,
total: data.pagination.total,
totalPages: data.pagination.totalPages,
page: data.pagination.page
page: data.pagination.page,
}));
} catch (err) {
toast.error(err.message || 'Impossible de charger les utilisateurs');
console.error('Error fetching users:', err);
} finally {
setLoading(false);
}
@@ -143,32 +132,18 @@ const UsersPageClient = () => {
.catch(() => {});
}, []);
// Effect to fetch users when sort or pagination change
useEffect(() => {
fetchUsers();
}, [sortBy, sortOrder, pagination.page, pagination.limit]);
// Handle pagination
const handlePageChange = (newPage) => {
setPagination(prev => ({ ...prev, page: newPage }));
};
const handleLimitChange = (newLimit) => {
setPagination(prev => ({
...prev,
limit: newLimit,
page: 1 // Reset to first page when changing limit
}));
};
// Handle sorting
const handlePageChange = (newPage) => setPagination(prev => ({ ...prev, page: newPage }));
const handleLimitChange = (newLimit) => setPagination(prev => ({ ...prev, limit: newLimit, page: 1 }));
const handleSort = (newSortBy) => {
const newSortOrder = sortBy === newSortBy && sortOrder === 'asc' ? 'desc' : 'asc';
setSortBy(newSortBy);
setSortOrder(newSortOrder);
};
// Format date helper
const formatDate = (dateString) => {
if (!dateString) return 'N/A';
try {
@@ -177,7 +152,7 @@ const UsersPageClient = () => {
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
minute: '2-digit',
});
} catch {
return 'Date invalide';
@@ -185,8 +160,7 @@ const UsersPageClient = () => {
};
return (
<div className="flex flex-col gap-6">
{/* Users Table */}
<>
<Card variant="default" padding="none">
<Table
columns={columns}
@@ -205,17 +179,22 @@ const UsersPageClient = () => {
total={pagination.total}
/>
</Card>
</div>
<UserEditModal
userId={editingUserId}
isOpen={!!editingUserId}
onClose={() => setEditingUserId(null)}
onSaved={fetchUsers}
/>
</>
);
};
const UsersPage = () => {
return (
<div className="flex flex-col gap-4 sm:gap-6 lg:gap-8">
<AdminHeader title="Utilisateurs" description="Gérez les comptes utilisateurs" />
<UsersPageClient />
</div>
);
};
const UsersPage = () => (
<div className="flex flex-col gap-4 sm:gap-6 lg:gap-8">
<AdminHeader title="Utilisateurs" description="Gérez les comptes utilisateurs" />
<UsersPageClient />
</div>
);
export default UsersPage;