add badge + pagination components
This commit is contained in:
19
dashboard/src/components/common/LoadingSpinner.tsx
Normal file
19
dashboard/src/components/common/LoadingSpinner.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
interface LoadingSpinnerProps {
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'w-4 h-4',
|
||||||
|
md: 'w-8 h-8',
|
||||||
|
lg: 'w-12 h-12',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={`${sizeClasses[size]} border-2 border-gray-200 border-t-indigo-600 rounded-full animate-spin`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
dashboard/src/components/common/Pagination.tsx
Normal file
47
dashboard/src/components/common/Pagination.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
interface PaginationProps {
|
||||||
|
page: number;
|
||||||
|
pages: number;
|
||||||
|
total: number;
|
||||||
|
pageSize: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Pagination({
|
||||||
|
page,
|
||||||
|
pages,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
onPageChange,
|
||||||
|
}: PaginationProps) {
|
||||||
|
const start = (page - 1) * pageSize + 1;
|
||||||
|
const end = Math.min(page * pageSize, total);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200">
|
||||||
|
<div className="text-sm text-gray-700">
|
||||||
|
Showing <span className="font-medium">{start}</span> to{' '}
|
||||||
|
<span className="font-medium">{end}</span> of{' '}
|
||||||
|
<span className="font-medium">{total}</span> results
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => onPageChange(page - 1)}
|
||||||
|
disabled={page <= 1}
|
||||||
|
className="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
<span className="text-sm text-gray-700">
|
||||||
|
Page {page} of {pages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => onPageChange(page + 1)}
|
||||||
|
disabled={page >= pages}
|
||||||
|
className="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
dashboard/src/components/common/SeverityBadge.tsx
Normal file
32
dashboard/src/components/common/SeverityBadge.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Severity } from '../../types/api';
|
||||||
|
|
||||||
|
interface SeverityBadgeProps {
|
||||||
|
severity: Severity;
|
||||||
|
showLabel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const severityConfig: Record<
|
||||||
|
Severity,
|
||||||
|
{ bg: string; text: string; label: string }
|
||||||
|
> = {
|
||||||
|
critical: { bg: 'bg-red-100', text: 'text-red-800', label: 'Critical' },
|
||||||
|
high: { bg: 'bg-orange-100', text: 'text-orange-800', label: 'High' },
|
||||||
|
medium: { bg: 'bg-yellow-100', text: 'text-yellow-800', label: 'Medium' },
|
||||||
|
low: { bg: 'bg-blue-100', text: 'text-blue-800', label: 'Low' },
|
||||||
|
info: { bg: 'bg-gray-100', text: 'text-gray-800', label: 'Info' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SeverityBadge({
|
||||||
|
severity,
|
||||||
|
showLabel = true,
|
||||||
|
}: SeverityBadgeProps) {
|
||||||
|
const config = severityConfig[severity];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${config.bg} ${config.text}`}
|
||||||
|
>
|
||||||
|
{showLabel && config.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
dashboard/src/components/deliberation/VerdictBadge.tsx
Normal file
53
dashboard/src/components/deliberation/VerdictBadge.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { Verdict } from '../../types/api';
|
||||||
|
|
||||||
|
interface VerdictBadgeProps {
|
||||||
|
verdict: Verdict | null;
|
||||||
|
confidence?: number | null;
|
||||||
|
size?: 'sm' | 'md';
|
||||||
|
}
|
||||||
|
|
||||||
|
const verdictConfig: Record<Verdict, { bg: string; text: string; icon: string; label: string }> = {
|
||||||
|
approve: {
|
||||||
|
bg: 'bg-green-100',
|
||||||
|
text: 'text-green-800',
|
||||||
|
icon: '✓',
|
||||||
|
label: 'Approved',
|
||||||
|
},
|
||||||
|
request_changes: {
|
||||||
|
bg: 'bg-red-100',
|
||||||
|
text: 'text-red-800',
|
||||||
|
icon: '✗',
|
||||||
|
label: 'Changes Requested',
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
bg: 'bg-blue-100',
|
||||||
|
text: 'text-blue-800',
|
||||||
|
icon: '💬',
|
||||||
|
label: 'Comment',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function VerdictBadge({ verdict, confidence, size = 'md' }: VerdictBadgeProps) {
|
||||||
|
if (!verdict) {
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = verdictConfig[verdict];
|
||||||
|
const sizeClasses = size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-3 py-1 text-sm';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center gap-1.5 rounded font-medium ${config.bg} ${config.text} ${sizeClasses}`}
|
||||||
|
>
|
||||||
|
<span>{config.icon}</span>
|
||||||
|
<span>{config.label}</span>
|
||||||
|
{confidence !== undefined && confidence !== null && (
|
||||||
|
<span className="opacity-75">({Math.round(confidence * 100)}%)</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user