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

View File

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

View File

@@ -81,7 +81,10 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
weeksMap[weekKey].push({ weeksMap[weekKey].push({
projectName: item.project_name, 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 => ({ weeksMap[currentWeekKey] = projects.map(project => ({
projectName: project.project_name || project.name, 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 => { })).filter(p => {
// Фильтруем по выбранному проекту, если он указан // Фильтруем по выбранному проекту, если он указан
if (selectedProject && p.projectName !== selectedProject) { if (selectedProject && p.projectName !== selectedProject) {
@@ -147,6 +151,7 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
// Функция для обработки данных недели // Функция для обработки данных недели
const processWeekData = (weekKey) => { const processWeekData = (weekKey) => {
const { year, week } = parseWeekKey(weekKey)
const weekProjects = weeksMap[weekKey] || [] const weekProjects = weeksMap[weekKey] || []
const totalScore = weekProjects.reduce((sum, p) => sum + p.score, 0) const totalScore = weekProjects.reduce((sum, p) => sum + p.score, 0)
@@ -154,15 +159,20 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
// Сортируем проекты так же, как в полной статистике (по priority и min_goal_score) // Сортируем проекты так же, как в полной статистике (по priority и min_goal_score)
// Получаем цвет проекта из данных full-statistics, если доступен // Получаем цвет проекта из данных full-statistics, если доступен
const projectsWithData = weekProjects.map(project => { const projectsWithData = weekProjects.map(project => {
// Ищем цвет проекта в данных full-statistics // Ищем данные проекта в full-statistics для получения цвета и calculated_score
const projectData = data?.find(item => item.project_name === project.projectName) const projectData = data?.find(item =>
item.project_name === project.projectName &&
item.report_year === year &&
item.report_week === week
)
const projectColor = projectData?.color const projectColor = projectData?.color
? getProjectColor(project.projectName, allProjects, projectData.color) ? getProjectColor(project.projectName, allProjects, projectData.color)
: getProjectColor(project.projectName, allProjects) : getProjectColor(project.projectName, allProjects)
return { return {
...project, ...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 => { const sortedProjectsWithData = sortedProjectNames.map(name => {
return projectsWithData.find(p => p.projectName === name) return projectsWithData.find(p => p.projectName === name)
}).filter(Boolean) }).filter(Boolean)
const { year, week } = parseWeekKey(weekKey)
return { return {
weekKey, weekKey,
@@ -212,49 +220,75 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
} }
// Компонент для отображения прогрессбара недели // Компонент для отображения прогрессбара недели
const WeekProgressBar = ({ weekData }) => ( const WeekProgressBar = ({ weekData }) => {
<div className="flex items-center gap-3"> // Находим выбранный проект в данных недели для отображения normalized значения
<div className="min-w-[100px] text-sm font-medium text-gray-700"> const selectedProjectData = selectedProject
Неделя {weekData.week} ? 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>
<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 ( return (
<div> <div>