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

202 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ProjectProgressBar from './ProjectProgressBar'
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 : []) || []
// Показываем loading только если данных нет и идет загрузка
if (loading && (!data || projectsData.length === 0)) {
return (
<div className="flex justify-center items-center py-16">
<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>
</div>
</div>
)
}
if (error && (!data || projectsData.length === 0)) {
return (
<div className="flex flex-col items-center justify-center py-16">
<div className="bg-red-50 border border-red-200 rounded-lg p-6 mb-4 max-w-md">
<div className="text-red-700 font-semibold mb-2">Ошибка загрузки</div>
<div className="text-red-600 text-sm">{error}</div>
</div>
<button
onClick={onRetry}
className="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-lg hover:from-indigo-700 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl font-semibold"
>
Попробовать снова
</button>
</div>
)
}
// Процент выполнения берем только из данных 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 || [])
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) => {
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
}) : []
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'}
</div>
</div>
{hasProgressData && typeof overallProgress === 'number' && Number.isFinite(overallProgress) && (
<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`}
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="Проекты"
>
<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>
</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
}
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}
onProjectClick={onProjectClick}
projectColor={projectColor}
priority={project.priority}
/>
</div>
)
})}
</div>
</div>
)
}
export default CurrentWeek