Files
play-life/play-life-web/src/components/CurrentWeek.jsx

190 lines
8.5 KiB
React
Raw Normal View History

2025-12-29 20:01:55 +03:00
import ProjectProgressBar from './ProjectProgressBar'
import LoadingError from './LoadingError'
2025-12-29 20:01:55 +03:00
import { getAllProjectsSorted, getProjectColor } from '../utils/projectUtils'
function CurrentWeek({ onProjectClick, data, loading, error, onRetry, allProjectsData, onNavigate }) {
// Обрабатываем данные: может быть объект с projects и total, или просто массив
const projectsData = data?.projects || (Array.isArray(data) ? data : []) || []
2025-12-29 20:01:55 +03:00
// Показываем loading только если данных нет и идет загрузка
if (loading && (!data || projectsData.length === 0)) {
return (
<div className="fixed inset-0 bottom-20 flex justify-center items-center">
2025-12-29 20:01:55 +03:00
<div className="flex flex-col items-center">
<div className="w-12 h-12 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin mb-4"></div>
<div className="text-gray-600 font-medium">Загрузка...</div>
2025-12-29 20:01:55 +03:00
</div>
</div>
)
}
if (error && (!data || projectsData.length === 0)) {
return <LoadingError onRetry={onRetry} />
2025-12-29 20:01:55 +03:00
}
// Процент выполнения берем только из данных API
const overallProgress = (() => {
// Проверяем различные возможные названия поля
const rawValue = data?.total ?? data?.progress ?? data?.percentage ?? data?.completion ?? data?.goal_progress
const parsedValue = rawValue === undefined || rawValue === null ? null : parseFloat(rawValue)
if (Number.isFinite(parsedValue) && parsedValue >= 0) {
return Math.max(0, parsedValue) // Убрали ограничение на 100, так как может быть больше
}
return null // null означает, что данные не пришли
})()
const hasProgressData = overallProgress !== null
// Логирование для отладки
console.log('CurrentWeek data:', {
data,
dataTotal: data?.total,
dataProgress: data?.progress,
dataPercentage: data?.percentage,
overallProgress,
hasProgressData
})
// Получаем отсортированный список всех проектов для синхронизации цветов
const allProjects = getAllProjectsSorted(allProjectsData, projectsData || [])
2025-12-29 20:01:55 +03:00
const normalizePriority = (value) => {
if (value === null || value === undefined) return Infinity
const numeric = Number(value)
return Number.isFinite(numeric) ? numeric : Infinity
}
// Сортируем: сначала по priority (1, 2, ...; null в конце), затем по min_goal_score по убыванию
const sortedData = (projectsData && projectsData.length > 0) ? [...projectsData].sort((a, b) => {
2025-12-29 20:01:55 +03:00
const priorityA = normalizePriority(a.priority)
const priorityB = normalizePriority(b.priority)
if (priorityA !== priorityB) {
return priorityA - priorityB
}
const minGoalA = parseFloat(a.min_goal_score) || 0
const minGoalB = parseFloat(b.min_goal_score) || 0
return minGoalB - minGoalA
}) : []
2025-12-29 20:01:55 +03:00
return (
<div>
{/* Информация об общем проценте выполнения целей */}
<div className="mb-3 bg-gradient-to-r from-indigo-50 to-purple-50 rounded-lg p-4 border border-indigo-200">
<div className="flex items-stretch justify-between gap-4">
<div className="min-w-0 flex-1 flex items-center gap-4">
<div className="flex-1">
<div className="text-sm sm:text-base text-gray-600 mb-1">Выполнение целей</div>
<div className="text-2xl sm:text-3xl lg:text-4xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
{hasProgressData && typeof overallProgress === 'number' && Number.isFinite(overallProgress)
? `${overallProgress.toFixed(1)}%`
: 'N/A'}
2025-12-29 20:01:55 +03:00
</div>
</div>
{hasProgressData && typeof overallProgress === 'number' && Number.isFinite(overallProgress) && (
2025-12-29 20:01:55 +03:00
<div className="w-12 h-12 sm:w-16 sm:h-16 relative flex-shrink-0">
<svg className="transform -rotate-90" viewBox="0 0 64 64">
<circle
cx="32"
cy="32"
r="28"
stroke="currentColor"
strokeWidth="6"
fill="none"
className="text-gray-200"
/>
<circle
cx="32"
cy="32"
r="28"
stroke="url(#gradient)"
strokeWidth="6"
fill="none"
strokeDasharray={`${Math.min(Math.max(overallProgress / 100, 0), 1) * 175.93} 175.93`}
2025-12-29 20:01:55 +03:00
strokeLinecap="round"
/>
{overallProgress >= 100 && (
<g className="transform rotate-90" style={{ transformOrigin: '32px 32px' }}>
<path
d="M 20 32 L 28 40 L 44 24"
stroke="url(#gradient)"
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</g>
)}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#4f46e5" />
<stop offset="100%" stopColor="#9333ea" />
</linearGradient>
</defs>
</svg>
</div>
)}
</div>
{onNavigate && (
<div className="flex flex-col flex-shrink-0 gap-1" style={{ minHeight: '64px', height: '100%' }}>
<button
onClick={() => onNavigate('full')}
className="flex-1 flex items-center justify-center px-4 bg-white hover:bg-indigo-50 text-indigo-600 hover:text-indigo-700 rounded-lg border border-indigo-200 hover:border-indigo-300 transition-all duration-200 shadow-sm hover:shadow-md"
title="Статистика"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
</button>
<button
onClick={() => onNavigate('priorities')}
className="flex-1 flex items-center justify-center px-4 bg-white hover:bg-indigo-50 text-indigo-600 hover:text-indigo-700 rounded-lg border border-indigo-200 hover:border-indigo-300 transition-all duration-200 shadow-sm hover:shadow-md"
title="Проекты"
2025-12-29 20:01:55 +03:00
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
2025-12-29 20:01:55 +03:00
</svg>
</button>
</div>
)}
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{sortedData.map((project, index) => {
if (!project || !project.project_name) {
return null
}
2025-12-29 20:01:55 +03:00
const projectColor = getProjectColor(project.project_name, allProjects)
return (
<div key={index}>
<ProjectProgressBar
projectName={project.project_name}
totalScore={parseFloat(project.total_score) || 0}
minGoalScore={parseFloat(project.min_goal_score) || 0}
maxGoalScore={parseFloat(project.max_goal_score) || 0}
2025-12-29 20:01:55 +03:00
onProjectClick={onProjectClick}
projectColor={projectColor}
priority={project.priority}
/>
</div>
)
})}
</div>
</div>
)
}
export default CurrentWeek