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' }}
|
style={{ backgroundColor: role.color || '#6b7280' }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<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 && (
|
{role.description && (
|
||||||
<div className="text-xs text-neutral-500 dark:text-gray-400">{role.description}</div>
|
<div className="text-xs text-neutral-500 dark:text-gray-400">{role.description}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{role.is_system && (
|
|
||||||
<Badge variant="default" size="sm">Système</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
skeleton: { height: 'h-4', width: '60%' }
|
skeleton: { height: 'h-4', width: '60%' }
|
||||||
@@ -54,6 +53,13 @@ const RolesPageClient = () => {
|
|||||||
),
|
),
|
||||||
skeleton: { height: 'h-4', width: '40px' }
|
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',
|
key: 'actions',
|
||||||
label: '',
|
label: '',
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import React from 'react';
|
|||||||
const Badge = ({
|
const Badge = ({
|
||||||
children,
|
children,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size = 'md',
|
size = 'sm',
|
||||||
className = '',
|
className = '',
|
||||||
...props
|
...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 = {
|
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',
|
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',
|
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',
|
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 = {
|
const sizes = {
|
||||||
sm: 'px-2 py-0.5 rounded-lg text-[12px]',
|
sm: 'px-[8px] py-[2px] rounded-lg text-[11px]',
|
||||||
md: 'px-2.5 py-0.5 rounded-lg text-[12px]',
|
md: 'px-[8px] py-[2px] rounded-lg text-[11px]',
|
||||||
lg: 'px-3 py-1 rounded-lg text-xs'
|
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