4.16.3: Добавлен normalized_total_score в прогресс недель
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m32s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m32s
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user