)
}
// Компонент карточки проекта с круглым прогрессбаром
function ProjectCard({ project, projectColor, onProjectClick }) {
const { project_name, total_score, min_goal_score, max_goal_score, priority } = project
// Вычисляем прогресс по оригинальной логике из ProjectProgressBar
const getGoalProgress = () => {
const safeTotal = Number.isFinite(total_score) ? total_score : 0
const safeMinGoal = Number.isFinite(min_goal_score) ? min_goal_score : 0
const safeMaxGoal = Number.isFinite(max_goal_score) ? max_goal_score : 0
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
})()
// Если нет валидного 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 goalProgress = getGoalProgress()
const maxProgressForPriority = 100 + (() => {
const normalizedPriority = (() => {
if (priority === null || priority === undefined) return null
const numeric = Number(priority)
return Number.isFinite(numeric) ? numeric : null
})()
if (normalizedPriority === 1) return 50
if (normalizedPriority === 2) return 35
return 20
})()
// Для визуального отображения: 100% прогрессбара = максимум для данного приоритета
// visualProgress показывает процент заполнения прогрессбара (0-100%), где 100% = maxProgressForPriority
const visualProgress = Math.min((goalProgress / maxProgressForPriority) * 100, 100)
// Для extra overlay: если goalProgress > 100%, показываем extra часть
// Но визуально это уже учтено в visualProgress, так что extra overlay не нужен
// Однако если нужно показать, что достигнут максимум, можно использовать другой подход
const baseVisualProgress = visualProgress
const extraVisualProgress = 0 // Не используем extra overlay, так как visualProgress уже показывает весь прогресс
// Вычисляем целевую зону
const getTargetZone = () => {
const safeMinGoal = Number.isFinite(min_goal_score) ? min_goal_score : 0
const safeMaxGoal = Number.isFinite(max_goal_score) ? max_goal_score : 0
if (safeMinGoal > 0 && safeMaxGoal > 0) {
return `${safeMinGoal.toFixed(0)} - ${safeMaxGoal.toFixed(0)}`
} else if (safeMinGoal > 0) {
return `${safeMinGoal.toFixed(0)}+`
}
return '0+'
}
const handleClick = () => {
if (onProjectClick) {
onProjectClick(project_name)
}
}
return (
{/* Верхняя часть с названием и прогрессом */}
{/* Левая часть - текст (название, баллы, целевая зона) */}
{project_name}
{total_score?.toFixed(1) || '0.0'}
Целевая зона: {getTargetZone()}
{/* Правая часть - круглый прогрессбар */}
)
}
// Компонент группы проектов по приоритету
function PriorityGroup({ title, subtitle, projects, allProjects, onProjectClick }) {
if (projects.length === 0) return null
return (
)
}
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 (
Загрузка...
)
}
if (error && (!data || projectsData.length === 0)) {
return
}
// Процент выполнения берем только из данных 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
// Получаем отсортированный список всех проектов для синхронизации цветов
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
}
// Группируем проекты по приоритетам
const priorityGroups = {
main: [], // priority === 1
important: [], // priority === 2
others: [] // остальные
}
if (projectsData && projectsData.length > 0) {
projectsData.forEach(project => {
if (!project || !project.project_name) return
const priority = normalizePriority(project.priority)
if (priority === 1) {
priorityGroups.main.push(project)
} else if (priority === 2) {
priorityGroups.important.push(project)
} else {
priorityGroups.others.push(project)
}
})
// Сортируем внутри каждой группы по min_goal_score по убыванию
Object.values(priorityGroups).forEach(group => {
group.sort((a, b) => {
const minGoalA = parseFloat(a.min_goal_score) || 0
const minGoalB = parseFloat(b.min_goal_score) || 0
return minGoalB - minGoalA
})
})
}
// Вычисляем процент выполнения для каждой группы
const calculateGroupProgress = (projects) => {
if (projects.length === 0) return 0
let totalProgress = 0
let validProjects = 0
projects.forEach(project => {
const safeTotal = Number.isFinite(project.total_score) ? project.total_score : 0
const safeMinGoal = Number.isFinite(project.min_goal_score) ? project.min_goal_score : 0
if (safeMinGoal > 0) {
const projectProgress = Math.min((safeTotal / safeMinGoal) * 100, 100)
totalProgress += projectProgress
validProjects++
}
})
return validProjects > 0 ? totalProgress / validProjects : 0
}
const mainProgress = calculateGroupProgress(priorityGroups.main)
const importantProgress = calculateGroupProgress(priorityGroups.important)
const othersProgress = calculateGroupProgress(priorityGroups.others)
// Пересчитываем общий прогресс как среднее от групповых процентов
const recalculatedOverallProgress = (() => {
const groups = []
if (priorityGroups.main.length > 0) groups.push(mainProgress)
if (priorityGroups.important.length > 0) groups.push(importantProgress)
if (priorityGroups.others.length > 0) groups.push(othersProgress)
if (groups.length === 0) return null
const average = groups.reduce((sum, progress) => sum + progress, 0) / groups.length
return Math.max(0, average) // Убираем ограничение на 100% для текста
})()
// Используем пересчитанный общий прогресс вместо API данных
const displayOverallProgress = recalculatedOverallProgress !== null ? recalculatedOverallProgress : (hasProgressData ? overallProgress : null)
return (
{/* Кнопка "Приоритеты" в правом верхнем углу */}
{onNavigate && (
)}
{/* Общий прогресс - большой круг в центре */}
onNavigate && onNavigate('full')}>
{/* Подсказка при наведении */}