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"`
|
MaxGoalScore *float64 `json:"max_goal_score,omitempty"`
|
||||||
Priority *int `json:"priority,omitempty"`
|
Priority *int `json:"priority,omitempty"`
|
||||||
CalculatedScore float64 `json:"calculated_score"`
|
CalculatedScore float64 `json:"calculated_score"`
|
||||||
|
TodayChange *float64 `json:"today_change,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupsProgress struct {
|
type GroupsProgress struct {
|
||||||
@@ -2536,6 +2537,14 @@ func (a *App) getWeeklyStatsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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 := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
p.id AS project_id,
|
p.id AS project_id,
|
||||||
@@ -2600,6 +2609,11 @@ func (a *App) getWeeklyStatsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
project.TotalScore = currentWeekScore
|
project.TotalScore = currentWeekScore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем сегодняшний прирост
|
||||||
|
if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 {
|
||||||
|
project.TodayChange = &todayScore
|
||||||
|
}
|
||||||
|
|
||||||
if minGoalScore.Valid {
|
if minGoalScore.Valid {
|
||||||
project.MinGoalScore = minGoalScore.Float64
|
project.MinGoalScore = minGoalScore.Float64
|
||||||
} else {
|
} else {
|
||||||
@@ -2939,6 +2953,80 @@ func (a *App) getCurrentWeekScores(userID int) (map[int]float64, error) {
|
|||||||
return scores, nil
|
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 получает данные текущей недели для всех пользователей
|
// getCurrentWeekScoresAllUsers получает данные текущей недели для всех пользователей
|
||||||
// Возвращает map[project_id]total_score для текущей недели
|
// Возвращает map[project_id]total_score для текущей недели
|
||||||
func (a *App) getCurrentWeekScoresAllUsers() (map[int]float64, error) {
|
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)
|
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 := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
p.id AS project_id,
|
p.id AS project_id,
|
||||||
@@ -3047,6 +3142,11 @@ func (a *App) getWeeklyStatsData() (*WeeklyStatsResponse, error) {
|
|||||||
project.TotalScore = currentWeekScore
|
project.TotalScore = currentWeekScore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем сегодняшний прирост
|
||||||
|
if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 {
|
||||||
|
project.TodayChange = &todayScore
|
||||||
|
}
|
||||||
|
|
||||||
if minGoalScore.Valid {
|
if minGoalScore.Valid {
|
||||||
project.MinGoalScore = minGoalScore.Float64
|
project.MinGoalScore = minGoalScore.Float64
|
||||||
} else {
|
} else {
|
||||||
@@ -3135,6 +3235,13 @@ func (a *App) getWeeklyStatsDataForUser(userID int) (*WeeklyStatsResponse, error
|
|||||||
return nil, fmt.Errorf("error getting current week scores: %w", err)
|
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 := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
p.id AS project_id,
|
p.id AS project_id,
|
||||||
@@ -3193,6 +3300,11 @@ func (a *App) getWeeklyStatsDataForUser(userID int) (*WeeklyStatsResponse, error
|
|||||||
project.TotalScore = currentWeekScore
|
project.TotalScore = currentWeekScore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем сегодняшний прирост
|
||||||
|
if todayScore, exists := todayScores[projectID]; exists && todayScore != 0 {
|
||||||
|
project.TodayChange = &todayScore
|
||||||
|
}
|
||||||
|
|
||||||
if minGoalScore.Valid {
|
if minGoalScore.Valid {
|
||||||
project.MinGoalScore = minGoalScore.Float64
|
project.MinGoalScore = minGoalScore.Float64
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "4.13.6",
|
"version": "4.14.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ function CircularProgressBar({ progress, size = 120, strokeWidth = 8, showCheckm
|
|||||||
|
|
||||||
// Компонент карточки проекта с круглым прогрессбаром
|
// Компонент карточки проекта с круглым прогрессбаром
|
||||||
function ProjectCard({ project, projectColor, onProjectClick }) {
|
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
|
// Вычисляем прогресс по оригинальной логике из ProjectProgressBar
|
||||||
const getGoalProgress = () => {
|
const getGoalProgress = () => {
|
||||||
@@ -155,6 +155,17 @@ function ProjectCard({ project, projectColor, onProjectClick }) {
|
|||||||
return '0+'
|
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 = () => {
|
const handleClick = () => {
|
||||||
if (onProjectClick) {
|
if (onProjectClick) {
|
||||||
onProjectClick(project_name)
|
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">
|
<div className="text-base font-semibold text-gray-600 leading-normal truncate mb-0.5">
|
||||||
{project_name}
|
{project_name}
|
||||||
</div>
|
</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'}
|
{total_score?.toFixed(1) || '0.0'}
|
||||||
</div>
|
</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">
|
<div className="text-xs text-gray-500 leading-normal">
|
||||||
Целевая зона: {getTargetZone()}
|
Целевая зона: {getTargetZone()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user