From e66a3cecce51be72d54f2ea629073dcfb015dced Mon Sep 17 00:00:00 2001 From: poignatov Date: Wed, 4 Feb 2026 15:04:58 +0300 Subject: [PATCH] =?UTF-8?q?4.14.0:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B4=D0=BD=D0=B5=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D1=80=D0=BE=D1=81=D1=82=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- play-life-backend/main.go | 112 +++++++++++++++++++ play-life-web/package.json | 2 +- play-life-web/src/components/CurrentWeek.jsx | 24 +++- 4 files changed, 135 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index d03dd2c..c412a4e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.13.6 +4.14.0 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 2594d29..6031f1f 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -112,6 +112,7 @@ type WeeklyProjectStats struct { MaxGoalScore *float64 `json:"max_goal_score,omitempty"` Priority *int `json:"priority,omitempty"` CalculatedScore float64 `json:"calculated_score"` + TodayChange *float64 `json:"today_change,omitempty"` } type GroupsProgress struct { @@ -2536,6 +2537,14 @@ func (a *App) getWeeklyStatsHandler(w http.ResponseWriter, r *http.Request) { return } + // Получаем сегодняшние приросты + todayScores, err := a.getTodayScores(userID) + if err != nil { + log.Printf("Error getting today scores: %v", err) + sendErrorWithCORS(w, err.Error(), http.StatusInternalServerError) + return + } + query := ` SELECT p.id AS project_id, @@ -2600,6 +2609,11 @@ func (a *App) getWeeklyStatsHandler(w http.ResponseWriter, r *http.Request) { project.TotalScore = currentWeekScore } + // Добавляем сегодняшний прирост + if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 { + project.TodayChange = &todayScore + } + if minGoalScore.Valid { project.MinGoalScore = minGoalScore.Float64 } else { @@ -2939,6 +2953,80 @@ func (a *App) getCurrentWeekScores(userID int) (map[int]float64, error) { return scores, nil } +// getTodayScores получает сумму score всех нод, созданных сегодня для конкретного пользователя +// Возвращает map[project_id]today_score для сегодняшнего дня +func (a *App) getTodayScores(userID int) (map[int]float64, error) { + query := ` + SELECT + n.project_id, + COALESCE(SUM(n.score), 0) AS today_score + FROM nodes n + JOIN projects p ON n.project_id = p.id + WHERE + p.deleted = FALSE + AND p.user_id = $1 + AND n.user_id = $1 + AND DATE(n.created_date) = CURRENT_DATE + GROUP BY n.project_id + ` + + rows, err := a.DB.Query(query, userID) + if err != nil { + log.Printf("Error querying today scores: %v", err) + return nil, fmt.Errorf("error querying today scores: %w", err) + } + defer rows.Close() + + scores := make(map[int]float64) + for rows.Next() { + var projectID int + var todayScore float64 + if err := rows.Scan(&projectID, &todayScore); err != nil { + log.Printf("Error scanning today scores row: %v", err) + return nil, fmt.Errorf("error scanning today scores row: %w", err) + } + scores[projectID] = todayScore + } + + return scores, nil +} + +// getTodayScoresAllUsers получает сумму score всех нод, созданных сегодня для всех пользователей +// Возвращает map[project_id]today_score для сегодняшнего дня +func (a *App) getTodayScoresAllUsers() (map[int]float64, error) { + query := ` + SELECT + n.project_id, + COALESCE(SUM(n.score), 0) AS today_score + FROM nodes n + JOIN projects p ON n.project_id = p.id + WHERE + p.deleted = FALSE + AND DATE(n.created_date) = CURRENT_DATE + GROUP BY n.project_id + ` + + rows, err := a.DB.Query(query) + if err != nil { + log.Printf("Error querying today scores for all users: %v", err) + return nil, fmt.Errorf("error querying today scores for all users: %w", err) + } + defer rows.Close() + + scores := make(map[int]float64) + for rows.Next() { + var projectID int + var todayScore float64 + if err := rows.Scan(&projectID, &todayScore); err != nil { + log.Printf("Error scanning today scores row: %v", err) + return nil, fmt.Errorf("error scanning today scores row: %w", err) + } + scores[projectID] = todayScore + } + + return scores, nil +} + // getCurrentWeekScoresAllUsers получает данные текущей недели для всех пользователей // Возвращает map[project_id]total_score для текущей недели func (a *App) getCurrentWeekScoresAllUsers() (map[int]float64, error) { @@ -2985,6 +3073,13 @@ func (a *App) getWeeklyStatsData() (*WeeklyStatsResponse, error) { return nil, fmt.Errorf("error getting current week scores: %w", err) } + // Получаем сегодняшние приросты для всех пользователей + todayScores, err := a.getTodayScoresAllUsers() + if err != nil { + log.Printf("Error getting today scores: %v", err) + return nil, fmt.Errorf("error getting today scores: %w", err) + } + query := ` SELECT p.id AS project_id, @@ -3047,6 +3142,11 @@ func (a *App) getWeeklyStatsData() (*WeeklyStatsResponse, error) { project.TotalScore = currentWeekScore } + // Добавляем сегодняшний прирост + if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 { + project.TodayChange = &todayScore + } + if minGoalScore.Valid { project.MinGoalScore = minGoalScore.Float64 } else { @@ -3135,6 +3235,13 @@ func (a *App) getWeeklyStatsDataForUser(userID int) (*WeeklyStatsResponse, error return nil, fmt.Errorf("error getting current week scores: %w", err) } + // Получаем сегодняшние приросты + todayScores, err := a.getTodayScores(userID) + if err != nil { + log.Printf("Error getting today scores: %v", err) + return nil, fmt.Errorf("error getting today scores: %w", err) + } + query := ` SELECT p.id AS project_id, @@ -3193,6 +3300,11 @@ func (a *App) getWeeklyStatsDataForUser(userID int) (*WeeklyStatsResponse, error project.TotalScore = currentWeekScore } + // Добавляем сегодняшний прирост + if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 { + project.TodayChange = &todayScore + } + if minGoalScore.Valid { project.MinGoalScore = minGoalScore.Float64 } else { diff --git a/play-life-web/package.json b/play-life-web/package.json index 63376bd..8c0e449 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "4.13.6", + "version": "4.14.0", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/CurrentWeek.jsx b/play-life-web/src/components/CurrentWeek.jsx index 952f206..5c73ec7 100644 --- a/play-life-web/src/components/CurrentWeek.jsx +++ b/play-life-web/src/components/CurrentWeek.jsx @@ -92,7 +92,7 @@ function CircularProgressBar({ progress, size = 120, strokeWidth = 8, showCheckm // Компонент карточки проекта с круглым прогрессбаром function ProjectCard({ project, projectColor, onProjectClick }) { - const { project_name, total_score, min_goal_score, max_goal_score, priority } = project + const { project_name, total_score, min_goal_score, max_goal_score, priority, today_change } = project // Вычисляем прогресс по оригинальной логике из ProjectProgressBar const getGoalProgress = () => { @@ -155,6 +155,17 @@ function ProjectCard({ project, projectColor, onProjectClick }) { return '0+' } + // Форматируем сегодняшний прирост + const formatTodayChange = (value) => { + if (value === null || value === undefined) return '0' + const rounded = Math.round(value * 10) / 10 + if (rounded === 0) return '0' + if (Number.isInteger(rounded)) { + return rounded > 0 ? `+${rounded}` : `${rounded}` + } + return rounded > 0 ? `+${rounded.toFixed(1)}` : `${rounded.toFixed(1)}` + } + const handleClick = () => { if (onProjectClick) { onProjectClick(project_name) @@ -173,8 +184,15 @@ function ProjectCard({ project, projectColor, onProjectClick }) {
{project_name}
-
- {total_score?.toFixed(1) || '0.0'} +
+
+ {total_score?.toFixed(1) || '0.0'} +
+ {today_change !== null && today_change !== undefined && today_change !== 0 && ( +
+ ({formatTodayChange(today_change)}) +
+ )}
Целевая зона: {getTargetZone()}