4.14.0: Добавлен дневной прирост в карточках
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m25s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m25s
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "play-life-web",
|
||||
"version": "4.13.6",
|
||||
"version": "4.14.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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,9 +184,16 @@ function ProjectCard({ project, projectColor, onProjectClick }) {
|
||||
<div className="text-base font-semibold text-gray-600 leading-normal truncate mb-0.5">
|
||||
{project_name}
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-black leading-normal mb-0.5">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<div className="text-3xl font-bold text-black leading-normal">
|
||||
{total_score?.toFixed(1) || '0.0'}
|
||||
</div>
|
||||
{today_change !== null && today_change !== undefined && today_change !== 0 && (
|
||||
<div className="text-base font-medium text-gray-400 leading-normal">
|
||||
({formatTodayChange(today_change)})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 leading-normal">
|
||||
Целевая зона: {getTargetZone()}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user