4.16.3: Добавлен normalized_total_score в прогресс недель
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m32s

This commit is contained in:
poignatov
2026-02-04 17:42:58 +03:00
parent e3e9084792
commit 62d36dca17
4 changed files with 113 additions and 65 deletions

View File

@@ -1 +1 @@
4.16.2
4.16.3

View File

@@ -210,13 +210,14 @@ type ProjectPriorityRequest struct {
}
type FullStatisticsItem struct {
ProjectName string `json:"project_name"`
ReportYear int `json:"report_year"`
ReportWeek int `json:"report_week"`
TotalScore float64 `json:"total_score"`
MinGoalScore float64 `json:"min_goal_score"`
MaxGoalScore float64 `json:"max_goal_score"`
Color string `json:"color"`
ProjectName string `json:"project_name"`
ReportYear int `json:"report_year"`
ReportWeek int `json:"report_week"`
TotalScore float64 `json:"total_score"`
MinGoalScore float64 `json:"min_goal_score"`
MaxGoalScore float64 `json:"max_goal_score"`
NormalizedTotalScore float64 `json:"normalized_total_score"`
Color string `json:"color"`
}
type TodayEntryNode struct {
@@ -6306,6 +6307,9 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
-- Фактический score: COALESCE(NULL, 0.0000)
COALESCE(wr.total_score, 0.0000) AS total_score,
-- Normalized score из MV
COALESCE(wr.normalized_total_score, 0.0000) AS normalized_total_score,
-- Минимальная цель: COALESCE(NULL, 0.0000)
COALESCE(wg.min_goal_score, 0.0000) AS min_goal_score,
@@ -6353,6 +6357,7 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
&item.ReportYear,
&item.ReportWeek,
&item.TotalScore,
&item.NormalizedTotalScore,
&item.MinGoalScore,
&item.MaxGoalScore,
&projectID,
@@ -6368,9 +6373,16 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
if item.ReportYear == currentYearInt && item.ReportWeek == currentWeekInt {
if score, exists := currentWeekScores[projectID]; exists {
item.TotalScore = score
// Для текущей недели normalized_total_score не отправляем
item.NormalizedTotalScore = 0
}
}
// Если normalized_total_score равен total_score, не отправляем его
if item.NormalizedTotalScore == item.TotalScore {
item.NormalizedTotalScore = 0
}
statistics = append(statistics, item)
}
@@ -6423,15 +6435,17 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
totalScore = score
}
// Для текущей недели normalized_total_score не отправляем
_, weekISO := time.Now().ISOWeek()
item := FullStatisticsItem{
ProjectName: projectName,
ReportYear: time.Now().Year(),
ReportWeek: weekISO,
TotalScore: totalScore,
MinGoalScore: minGoalScore,
MaxGoalScore: maxGoalScore,
Color: projectColor,
ProjectName: projectName,
ReportYear: time.Now().Year(),
ReportWeek: weekISO,
TotalScore: totalScore,
NormalizedTotalScore: 0,
MinGoalScore: minGoalScore,
MaxGoalScore: maxGoalScore,
Color: projectColor,
}
statistics = append(statistics, item)
}

View File

@@ -1,6 +1,6 @@
{
"name": "play-life-web",
"version": "4.16.2",
"version": "4.16.3",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -81,7 +81,10 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
weeksMap[weekKey].push({
projectName: item.project_name,
score: parseFloat(item.total_score) || 0
score: parseFloat(item.total_score) || 0,
minGoalScore: parseFloat(item.min_goal_score) || 0,
maxGoalScore: parseFloat(item.max_goal_score) || 0,
normalizedTotalScore: parseFloat(item.normalized_total_score) || 0
})
})
}
@@ -94,7 +97,8 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
weeksMap[currentWeekKey] = projects.map(project => ({
projectName: project.project_name || project.name,
score: parseFloat(project.total_score || project.score || 0)
score: parseFloat(project.total_score || project.score || 0),
normalizedTotalScore: parseFloat(project.normalized_total_score) || 0
})).filter(p => {
// Фильтруем по выбранному проекту, если он указан
if (selectedProject && p.projectName !== selectedProject) {
@@ -147,6 +151,7 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
// Функция для обработки данных недели
const processWeekData = (weekKey) => {
const { year, week } = parseWeekKey(weekKey)
const weekProjects = weeksMap[weekKey] || []
const totalScore = weekProjects.reduce((sum, p) => sum + p.score, 0)
@@ -154,15 +159,20 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
// Сортируем проекты так же, как в полной статистике (по priority и min_goal_score)
// Получаем цвет проекта из данных full-statistics, если доступен
const projectsWithData = weekProjects.map(project => {
// Ищем цвет проекта в данных full-statistics
const projectData = data?.find(item => item.project_name === project.projectName)
// Ищем данные проекта в full-statistics для получения цвета и calculated_score
const projectData = data?.find(item =>
item.project_name === project.projectName &&
item.report_year === year &&
item.report_week === week
)
const projectColor = projectData?.color
? getProjectColor(project.projectName, allProjects, projectData.color)
: getProjectColor(project.projectName, allProjects)
return {
...project,
color: projectColor
color: projectColor,
normalizedTotalScore: projectData?.normalized_total_score !== undefined ? parseFloat(projectData.normalized_total_score) || 0 : project.normalizedTotalScore || 0
}
})
@@ -176,8 +186,6 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
const sortedProjectsWithData = sortedProjectNames.map(name => {
return projectsWithData.find(p => p.projectName === name)
}).filter(Boolean)
const { year, week } = parseWeekKey(weekKey)
return {
weekKey,
@@ -212,49 +220,75 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
}
// Компонент для отображения прогрессбара недели
const WeekProgressBar = ({ weekData }) => (
<div className="flex items-center gap-3">
<div className="min-w-[100px] text-sm font-medium text-gray-700">
Неделя {weekData.week}
const WeekProgressBar = ({ weekData }) => {
// Находим выбранный проект в данных недели для отображения normalized значения
const selectedProjectData = selectedProject
? weekData.projects.find(p => p.projectName === selectedProject)
: null
return (
<div className="flex items-center gap-3">
<div className="min-w-[85px] text-sm font-medium text-gray-700">
Неделя {weekData.week}
</div>
<div className="flex-1 relative h-6 bg-gray-200 rounded-full overflow-hidden shadow-inner">
{weekData.totalScore === 0 ? (
<div className="absolute inset-0 flex items-center justify-center text-gray-400 text-xs">
Нет данных
</div>
) : (
<>
{weekData.projects.map((project, index) => {
// Вычисляем позицию и ширину для каждого сегмента на основе абсолютных значений
let left = 0
for (let i = 0; i < index; i++) {
left += weekData.projects[i].score
}
const widthPercent = (project.score / maxTotalScore) * 100
const leftPercent = (left / maxTotalScore) * 100
return (
<div
key={project.projectName}
className="absolute h-full transition-all duration-300 hover:opacity-90"
style={{
left: `${leftPercent}%`,
width: `${widthPercent}%`,
backgroundColor: project.color,
}}
title={`${project.projectName}: ${project.score.toFixed(1)} баллов`}
/>
)
})}
</>
)}
</div>
<div className="min-w-[85px] text-right text-sm text-gray-600 font-medium">
{weekData.totalScore > 0 ? (
<>
{selectedProjectData &&
selectedProjectData.normalizedTotalScore !== undefined &&
selectedProjectData.normalizedTotalScore !== null &&
selectedProjectData.normalizedTotalScore > 0 &&
Math.abs(selectedProjectData.normalizedTotalScore - selectedProjectData.score) > 0.01 ? (
<>
<span className="text-gray-400">
({selectedProjectData.normalizedTotalScore.toFixed(1)})
</span>
<span className="ml-1">
{selectedProjectData.score.toFixed(1)}
</span>
</>
) : (
weekData.totalScore.toFixed(1)
)}
</>
) : '-'}
</div>
</div>
<div className="flex-1 relative h-6 bg-gray-200 rounded-full overflow-hidden shadow-inner">
{weekData.totalScore === 0 ? (
<div className="absolute inset-0 flex items-center justify-center text-gray-400 text-xs">
Нет данных
</div>
) : (
<>
{weekData.projects.map((project, index) => {
// Вычисляем позицию и ширину для каждого сегмента на основе абсолютных значений
let left = 0
for (let i = 0; i < index; i++) {
left += weekData.projects[i].score
}
const widthPercent = (project.score / maxTotalScore) * 100
const leftPercent = (left / maxTotalScore) * 100
return (
<div
key={project.projectName}
className="absolute h-full transition-all duration-300 hover:opacity-90"
style={{
left: `${leftPercent}%`,
width: `${widthPercent}%`,
backgroundColor: project.color,
}}
title={`${project.projectName}: ${project.score.toFixed(1)} баллов`}
/>
)
})}
</>
)}
</div>
<div className="min-w-[60px] text-right text-sm text-gray-600 font-medium">
{weekData.totalScore > 0 ? `${weekData.totalScore.toFixed(1)}` : '-'}
</div>
</div>
)
)
}
return (
<div>