feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import { IconCheck, IconX } from '@tabler/icons-react'
|
||||
|
||||
/**
|
||||
* ProgressBar - компонент прогресс бара с estimated time
|
||||
* @param {number} progress - прогресс от 0 до 100
|
||||
* @param {string} status - статус операции
|
||||
* @param {number} estimatedTime - оставшееся время в секундах
|
||||
* @param {string} variant - цвет: 'primary', 'success', 'danger', 'warning'
|
||||
*/
|
||||
function ProgressBar({
|
||||
progress = 0,
|
||||
status = '',
|
||||
estimatedTime = null,
|
||||
variant = 'primary',
|
||||
showPercentage = true,
|
||||
striped = false,
|
||||
animated = false,
|
||||
size = 'default' // 'sm', 'default', 'lg'
|
||||
}) {
|
||||
const sizeClass = size === 'sm' ? 'progress-sm' : size === 'lg' ? 'progress-lg' : ''
|
||||
const stripedClass = striped ? 'progress-bar-striped' : ''
|
||||
const animatedClass = animated ? 'progress-bar-animated' : ''
|
||||
|
||||
const formatTime = (seconds) => {
|
||||
if (seconds < 60) {
|
||||
return `${Math.round(seconds)} сек`
|
||||
} else if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = Math.round(seconds % 60)
|
||||
return `${minutes} мин ${secs} сек`
|
||||
} else {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
return `${hours} ч ${minutes} мин`
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="progress-container">
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
{status && (
|
||||
<div className="text-muted small">{status}</div>
|
||||
)}
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
{estimatedTime !== null && estimatedTime > 0 && (
|
||||
<span className="text-muted small">
|
||||
Осталось ~{formatTime(estimatedTime)}
|
||||
</span>
|
||||
)}
|
||||
{showPercentage && (
|
||||
<span className="text-muted small fw-bold">
|
||||
{Math.round(progress)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`progress ${sizeClass}`}>
|
||||
<div
|
||||
className={`progress-bar bg-${variant} ${stripedClass} ${animatedClass}`}
|
||||
role="progressbar"
|
||||
style={{ width: `${progress}%` }}
|
||||
aria-valuenow={progress}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{size === 'lg' && showPercentage && (
|
||||
<span className="progress-bar-label">{Math.round(progress)}%</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-sm {
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-lg {
|
||||
height: 2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.progress-bar-label {
|
||||
line-height: 2rem;
|
||||
color: white;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* MultiStepProgress - компонент для отображения многошагового прогресса
|
||||
*/
|
||||
function MultiStepProgress({ steps, currentStep, variant = 'primary' }) {
|
||||
const progress = ((currentStep) / steps.length) * 100
|
||||
|
||||
return (
|
||||
<div className="multi-step-progress">
|
||||
{/* Индикаторы шагов */}
|
||||
<div className="steps-indicator mb-3">
|
||||
{steps.map((step, index) => {
|
||||
const isPast = index < currentStep
|
||||
const isCurrent = index === currentStep
|
||||
const status = isPast ? 'completed' : isCurrent ? 'current' : 'pending'
|
||||
|
||||
return (
|
||||
<div key={index} className={`step-item step-${status}`}>
|
||||
<div className={`step-circle bg-${isPast ? 'success' : isCurrent ? variant : 'secondary'}`}>
|
||||
{isPast ? (
|
||||
<IconCheck size={16} className="text-white" />
|
||||
) : (
|
||||
<span className="text-white">{index + 1}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="step-label text-muted small mt-1">{step}</div>
|
||||
{index < steps.length - 1 && (
|
||||
<div className={`step-line ${isPast ? 'completed' : ''}`}></div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Прогресс бар */}
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
variant={variant}
|
||||
showPercentage={false}
|
||||
animated
|
||||
/>
|
||||
|
||||
<style jsx>{`
|
||||
.steps-indicator {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 50%;
|
||||
right: -50%;
|
||||
height: 2px;
|
||||
background: var(--tblr-border-color, #e9ecef);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.step-line.completed {
|
||||
background: var(--tblr-success, #2fb344);
|
||||
}
|
||||
|
||||
.step-label {
|
||||
text-align: center;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.step-current .step-label {
|
||||
font-weight: 600;
|
||||
color: var(--tblr-body-color) !important;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ProgressBar, MultiStepProgress }
|
||||
export default ProgressBar
|
||||
|
||||
Reference in New Issue
Block a user