Initial commit
This commit is contained in:
158
play-life-web/src/components/ProjectProgressBar.jsx
Normal file
158
play-life-web/src/components/ProjectProgressBar.jsx
Normal file
@@ -0,0 +1,158 @@
|
||||
function ProjectProgressBar({ projectName, totalScore, minGoalScore, maxGoalScore, onProjectClick, projectColor, priority }) {
|
||||
// Вычисляем максимальное значение для шкалы (берем максимум из maxGoalScore и totalScore + 20%)
|
||||
const maxScale = Math.max(maxGoalScore, totalScore * 1.2, 1)
|
||||
|
||||
// Процентные значения
|
||||
const totalScorePercent = (totalScore / maxScale) * 100
|
||||
const minGoalPercent = (minGoalScore / maxScale) * 100
|
||||
const maxGoalPercent = (maxGoalScore / maxScale) * 100
|
||||
const goalRangePercent = maxGoalPercent - minGoalPercent
|
||||
|
||||
const normalizedPriority = (() => {
|
||||
if (priority === null || priority === undefined) return null
|
||||
const numeric = Number(priority)
|
||||
return Number.isFinite(numeric) ? numeric : null
|
||||
})()
|
||||
|
||||
const priorityBonus = (() => {
|
||||
if (normalizedPriority === 1) return 50
|
||||
if (normalizedPriority === 2) return 35
|
||||
return 20
|
||||
})()
|
||||
|
||||
const goalProgress = (() => {
|
||||
const safeTotal = Number.isFinite(totalScore) ? totalScore : 0
|
||||
const safeMinGoal = Number.isFinite(minGoalScore) ? minGoalScore : 0
|
||||
const safeMaxGoal = Number.isFinite(maxGoalScore) ? maxGoalScore : 0
|
||||
|
||||
// Если нет валидного minGoal, возвращаем прогресс относительно maxGoal либо 0
|
||||
if (safeMinGoal <= 0) {
|
||||
if (safeMaxGoal > 0) {
|
||||
return Math.max(0, Math.min((safeTotal / safeMaxGoal) * 100, 100))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// До достижения minGoal растем линейно от 0 до 100%
|
||||
const baseProgress = Math.max(0, Math.min((safeTotal / safeMinGoal) * 100, 100))
|
||||
|
||||
// Если maxGoal не задан корректно или еще не достигнут minGoal, показываем базовый прогресс
|
||||
if (safeTotal < safeMinGoal || safeMaxGoal <= safeMinGoal) {
|
||||
return baseProgress
|
||||
}
|
||||
|
||||
// Между minGoal и maxGoal добавляем бонус в зависимости от приоритета
|
||||
const extraRange = safeMaxGoal - safeMinGoal
|
||||
const extraRatio = Math.min(1, Math.max(0, (safeTotal - safeMinGoal) / extraRange))
|
||||
const extraProgress = extraRatio * priorityBonus
|
||||
|
||||
// Выше maxGoal прогресс не растет
|
||||
return Math.min(100 + priorityBonus, 100 + extraProgress)
|
||||
})()
|
||||
|
||||
const isGoalReached = totalScore >= minGoalScore
|
||||
const isGoalExceeded = totalScore >= maxGoalScore
|
||||
|
||||
const priorityBorderStyle =
|
||||
normalizedPriority === 1
|
||||
? { borderColor: '#d4af37' }
|
||||
: normalizedPriority === 2
|
||||
? { borderColor: '#c0c0c0' }
|
||||
: {}
|
||||
|
||||
const cardBorderClasses =
|
||||
normalizedPriority === 1 || normalizedPriority === 2
|
||||
? 'border-2'
|
||||
: 'border border-gray-200/50 hover:border-indigo-300'
|
||||
|
||||
const cardBaseClasses =
|
||||
'bg-gradient-to-br from-white to-gray-50 rounded-lg p-3 shadow-sm hover:shadow-md transition-all duration-300 cursor-pointer'
|
||||
|
||||
const handleClick = () => {
|
||||
if (onProjectClick) {
|
||||
onProjectClick(projectName)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className={`${cardBaseClasses} ${cardBorderClasses}`}
|
||||
style={priorityBorderStyle}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: projectColor }}
|
||||
></div>
|
||||
<h3 className="text-lg font-semibold text-gray-800">{projectName}</h3>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
||||
{totalScore.toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
из {minGoalScore.toFixed(1)} ({goalProgress.toFixed(0)}%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative h-6 bg-gray-200 rounded-full overflow-hidden shadow-inner">
|
||||
{/* Диапазон цели (min_goal_score до max_goal_score) */}
|
||||
{minGoalScore > 0 && maxGoalScore > 0 && (
|
||||
<div
|
||||
className="absolute h-full bg-gradient-to-r from-amber-200 via-yellow-300 to-amber-200 opacity-70 border-l border-r border-amber-400"
|
||||
style={{
|
||||
left: `${minGoalPercent}%`,
|
||||
width: `${goalRangePercent}%`,
|
||||
}}
|
||||
title={`Цель: ${minGoalScore.toFixed(2)} - ${maxGoalScore.toFixed(2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Текущее значение (total_score) */}
|
||||
<div
|
||||
className={`absolute h-full transition-all duration-700 ease-out ${
|
||||
isGoalExceeded
|
||||
? 'bg-gradient-to-r from-green-500 to-emerald-500'
|
||||
: isGoalReached
|
||||
? 'bg-gradient-to-r from-yellow-500 to-amber-500'
|
||||
: 'bg-gradient-to-r from-indigo-500 to-purple-500'
|
||||
} shadow-sm`}
|
||||
style={{
|
||||
width: `${totalScorePercent}%`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-white/20 animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
{/* Индикатор текущего значения */}
|
||||
{totalScorePercent > 0 && (
|
||||
<div
|
||||
className="absolute top-0 h-full w-0.5 bg-white shadow-md"
|
||||
style={{
|
||||
left: `${totalScorePercent}%`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center text-xs mt-1.5">
|
||||
<span className="text-gray-400">0</span>
|
||||
{minGoalScore > 0 && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-400"></div>
|
||||
<span className="text-amber-700 font-medium text-xs">
|
||||
Цель: {minGoalScore.toFixed(1)} - {maxGoalScore.toFixed(1)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-gray-400">{maxScale.toFixed(1)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectProgressBar
|
||||
|
||||
Reference in New Issue
Block a user