refactor(ui): move system badge to dedicated column and update Badge styles
This commit is contained in:
@@ -24,14 +24,13 @@ const RolesPageClient = () => {
|
||||
style={{ backgroundColor: role.color || '#6b7280' }}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-neutral-900 dark:text-white">{role.name}</div>
|
||||
<div className="text-sm font-medium text-neutral-900 dark:text-white">
|
||||
{role.name}
|
||||
</div>
|
||||
{role.description && (
|
||||
<div className="text-xs text-neutral-500 dark:text-gray-400">{role.description}</div>
|
||||
)}
|
||||
</div>
|
||||
{role.is_system && (
|
||||
<Badge variant="default" size="sm">Système</Badge>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
skeleton: { height: 'h-4', width: '60%' }
|
||||
@@ -54,6 +53,13 @@ const RolesPageClient = () => {
|
||||
),
|
||||
skeleton: { height: 'h-4', width: '40px' }
|
||||
},
|
||||
{
|
||||
key: 'is_system',
|
||||
label: 'Système',
|
||||
sortable: false,
|
||||
render: (role) => role.is_system ? <Badge variant="default" size="sm">système</Badge> : null,
|
||||
skeleton: { height: 'h-4', width: '60px' }
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: '',
|
||||
|
||||
@@ -5,14 +5,14 @@ import React from 'react';
|
||||
const Badge = ({
|
||||
children,
|
||||
variant = 'default',
|
||||
size = 'md',
|
||||
size = 'sm',
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
const baseClassName = 'inline-flex items-center font-medium border';
|
||||
const baseClassName = 'inline-flex items-center font-medium border font-ibm-plex-mono';
|
||||
|
||||
const variants = {
|
||||
default: 'bg-neutral-200/80 text-neutral-700 border-neutral-300 dark:bg-neutral-500/10 dark:text-neutral-400 dark:border-neutral-500/20',
|
||||
default: 'bg-neutral-100 text-neutral-700 border-neutral-200 dark:bg-neutral-500/10 dark:text-neutral-400 dark:border-neutral-500/20',
|
||||
primary: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-500/10 dark:text-blue-400 dark:border-blue-500/20',
|
||||
success: 'bg-green-100 text-green-700 border-green-200 dark:bg-green-500/10 dark:text-green-400 dark:border-green-500/20',
|
||||
warning: 'bg-yellow-100 text-yellow-700 border-yellow-200 dark:bg-yellow-500/10 dark:text-yellow-400 dark:border-yellow-500/20',
|
||||
@@ -24,8 +24,8 @@ const Badge = ({
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'px-2 py-0.5 rounded-lg text-[12px]',
|
||||
md: 'px-2.5 py-0.5 rounded-lg text-[12px]',
|
||||
sm: 'px-[8px] py-[2px] rounded-lg text-[11px]',
|
||||
md: 'px-[8px] py-[2px] rounded-lg text-[11px]',
|
||||
lg: 'px-3 py-1 rounded-lg text-xs'
|
||||
};
|
||||
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Button from './Button';
|
||||
import { Skeleton } from './LoadingState';
|
||||
|
||||
const Pagination = ({
|
||||
currentPage = 1,
|
||||
totalPages = 1,
|
||||
onPageChange,
|
||||
onLimitChange,
|
||||
limit = 20,
|
||||
total = 0,
|
||||
loading = false,
|
||||
showPerPage = true,
|
||||
showStats = true,
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
// Generate page numbers with ellipsis
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const delta = 2;
|
||||
|
||||
if (totalPages > 0) pages.push(1);
|
||||
if (currentPage - delta > 2) pages.push('...');
|
||||
|
||||
for (let i = Math.max(2, currentPage - delta); i <= Math.min(totalPages - 1, currentPage + delta); i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
if (currentPage + delta < totalPages - 1) pages.push('...');
|
||||
if (totalPages > 1) pages.push(totalPages);
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const PaginationButton = ({ onClick, disabled, children, isActive = false }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled || loading}
|
||||
className={`px-2 py-1 text-xs font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
|
||||
isActive
|
||||
? 'bg-neutral-900 text-white border border-neutral-900 dark:bg-white dark:text-black dark:border-white'
|
||||
: 'text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-700/30 hover:text-neutral-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (totalPages <= 1 && !showPerPage && !loading) return null;
|
||||
|
||||
const from = total > 0 ? (currentPage - 1) * limit + 1 : 0;
|
||||
const to = Math.min(currentPage * limit, total);
|
||||
|
||||
return (
|
||||
<div className={`px-6 py-3 border-t border-neutral-200 dark:border-neutral-700/30 ${className}`} {...props}>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
|
||||
{/* Per Page Selector */}
|
||||
{showPerPage ? (
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{loading ? (
|
||||
<Skeleton height="h-7" width="90px" />
|
||||
) : (
|
||||
<>
|
||||
<span className="text-xs text-neutral-500 dark:text-neutral-400">Afficher</span>
|
||||
<select
|
||||
value={limit}
|
||||
onChange={(e) => onLimitChange?.(Number(e.target.value))}
|
||||
disabled={loading}
|
||||
className="h-7 px-2 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700/50 rounded-md text-xs text-neutral-900 dark:text-white focus:outline-none focus:border-neutral-400 dark:focus:border-neutral-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={20}>20</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={100}>100</option>
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : <div />}
|
||||
|
||||
{/* Pagination Controls */}
|
||||
<div className="flex items-center gap-1">
|
||||
{loading ? (
|
||||
<>
|
||||
<Skeleton height="h-7" width="64px" />
|
||||
<div className="hidden sm:flex items-center gap-1">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<Skeleton key={i} height="h-7" width="28px" />
|
||||
))}
|
||||
</div>
|
||||
<Skeleton height="h-7" width="56px" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{totalPages > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Précédent
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="hidden sm:flex items-center gap-1">
|
||||
{getPageNumbers().map((page, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{page === '...' ? (
|
||||
<span className="px-2 text-xs text-neutral-400 dark:text-neutral-500">…</span>
|
||||
) : (
|
||||
<PaginationButton
|
||||
onClick={() => onPageChange(page)}
|
||||
isActive={currentPage === page}
|
||||
>
|
||||
{page}
|
||||
</PaginationButton>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Suivant
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
{showStats ? (
|
||||
<div className="text-xs text-neutral-400 dark:text-neutral-500 shrink-0 text-right">
|
||||
{loading ? (
|
||||
<Skeleton height="h-4" width="100px" />
|
||||
) : (
|
||||
`${from}–${to} sur ${total}`
|
||||
)}
|
||||
</div>
|
||||
) : <div />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
Reference in New Issue
Block a user