Files
router-lists-ui/frontend/src/components/ProgressIndicator.jsx
T

167 lines
4.5 KiB
React

import { IconX } from '@tabler/icons-react'
/**
* Компонент для отображения прогресса долгих операций
* Показывает progress bar и возможность отмены
*/
function ProgressIndicator({
progress = 0, // 0-100
total = 100,
current = 0,
message = 'Обработка...',
onCancel = null,
variant = 'blue', // blue, green, red, yellow, purple
showPercentage = true,
showCount = true,
animated = true,
className = ''
}) {
const percentage = total > 0 ? Math.min(100, Math.max(0, (current / total) * 100)) : progress
return (
<div className={`card ${className}`}>
<div className="card-body">
<div className="d-flex justify-content-between align-items-center mb-2">
<div className="text-muted">{message}</div>
{onCancel && (
<button
className="btn btn-sm btn-ghost-secondary btn-icon"
onClick={onCancel}
title="Отменить"
>
<IconX size={18} />
</button>
)}
</div>
{/* Progress bar */}
<div className="progress mb-2" style={{ height: '0.5rem' }}>
<div
className={`progress-bar ${animated ? 'progress-bar-striped progress-bar-animated' : ''} bg-${variant}`}
role="progressbar"
style={{ width: `${percentage}%` }}
aria-valuenow={percentage}
aria-valuemin="0"
aria-valuemax="100"
>
</div>
</div>
{/* Статистика */}
<div className="d-flex justify-content-between align-items-center">
{showCount && total > 0 && (
<div className="text-muted small">
Обработано: <strong>{current}</strong> из <strong>{total}</strong>
</div>
)}
{showPercentage && (
<div className={`text-${variant} fw-bold`}>
{percentage.toFixed(0)}%
</div>
)}
</div>
</div>
</div>
)
}
/**
* Линейный progress indicator (более компактный)
*/
export function LinearProgress({
progress = 0,
variant = 'blue',
message = null,
className = ''
}) {
return (
<div className={className}>
{message && (
<div className="text-muted small mb-1">{message}</div>
)}
<div className="progress" style={{ height: '0.25rem' }}>
<div
className={`progress-bar progress-bar-indeterminate bg-${variant}`}
style={progress > 0 ? { width: `${progress}%` } : {}}
></div>
</div>
</div>
)
}
/**
* Circular progress (кольцевой индикатор)
*/
export function CircularProgress({
progress = 0,
size = 64,
strokeWidth = 4,
variant = 'blue',
showPercentage = true,
className = ''
}) {
const radius = (size - strokeWidth) / 2
const circumference = radius * 2 * Math.PI
const offset = circumference - (progress / 100) * circumference
// Цвета Tabler
const colors = {
blue: '#206bc4',
green: '#2fb344',
red: '#d63939',
yellow: '#f76707',
purple: '#ae3ec9',
azure: '#4299e1'
}
return (
<div className={`d-inline-flex align-items-center justify-content-center ${className}`}>
<svg width={size} height={size}>
{/* Фоновое кольцо */}
<circle
stroke="#e9ecef"
fill="transparent"
strokeWidth={strokeWidth}
r={radius}
cx={size / 2}
cy={size / 2}
/>
{/* Прогресс */}
<circle
stroke={colors[variant] || colors.blue}
fill="transparent"
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
r={radius}
cx={size / 2}
cy={size / 2}
style={{
transform: 'rotate(-90deg)',
transformOrigin: '50% 50%',
transition: 'stroke-dashoffset 0.3s ease'
}}
/>
{/* Процент в центре */}
{showPercentage && (
<text
x="50%"
y="50%"
textAnchor="middle"
dy=".3em"
fontSize="14"
fontWeight="600"
fill={colors[variant] || colors.blue}
>
{Math.round(progress)}%
</text>
)}
</svg>
</div>
)
}
export default ProgressIndicator