diff --git a/VERSION b/VERSION index ac14c3d..61fcc87 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1.1 +5.1.2 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 24707b3..d0d2f46 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -11125,12 +11125,22 @@ func (a *App) syncFitbitData(userID int, date time.Time) error { if err := a.saveFitbitSubtaskDraft(userID, int(stepsGoalTaskID.Int64), int(stepsGoalSubtaskID.Int64), goalReached); err != nil { log.Printf("Error saving steps goal subtask draft: %v", err) } + } else if stepsGoalTaskID.Valid { + goalReached := steps >= goalSteps + if err := a.setFitbitTaskDraftAutoComplete(userID, int(stepsGoalTaskID.Int64), goalReached); err != nil { + log.Printf("Error setting steps goal task draft auto_complete: %v", err) + } } if floorsGoalTaskID.Valid && floorsGoalSubtaskID.Valid { goalReached := floors >= goalFloors if err := a.saveFitbitSubtaskDraft(userID, int(floorsGoalTaskID.Int64), int(floorsGoalSubtaskID.Int64), goalReached); err != nil { log.Printf("Error saving floors goal subtask draft: %v", err) } + } else if floorsGoalTaskID.Valid { + goalReached := floors >= goalFloors + if err := a.setFitbitTaskDraftAutoComplete(userID, int(floorsGoalTaskID.Int64), goalReached); err != nil { + log.Printf("Error setting floors goal task draft auto_complete: %v", err) + } } log.Printf("Fitbit data synced for user_id=%d, date=%s: steps=%d, floors=%d, goalSteps=%d, goalFloors=%d", @@ -11248,6 +11258,50 @@ func (a *App) saveFitbitSubtaskDraft(userID int, taskID int, subtaskID int, chec return nil } +// setFitbitTaskDraftAutoComplete создаёт или обновляет драфт задачи, выставляя только флаг «Выполнить в конце дня». +// Используется для достижения цели по шагам/этажам, когда выбрана задача без подзадачи. +func (a *App) setFitbitTaskDraftAutoComplete(userID int, taskID int, autoComplete bool) error { + var exists bool + err := a.DB.QueryRow(` + SELECT EXISTS(SELECT 1 FROM tasks WHERE id = $1 AND user_id = $2 AND deleted = FALSE) + `, taskID, userID).Scan(&exists) + if err != nil || !exists { + return fmt.Errorf("task %d not found or not owned by user", taskID) + } + + var draftID int + err = a.DB.QueryRow("SELECT id FROM task_drafts WHERE task_id = $1", taskID).Scan(&draftID) + if err == sql.ErrNoRows { + if !autoComplete { + return nil + } + _, err = a.DB.Exec(` + INSERT INTO task_drafts (task_id, user_id, auto_complete, created_at, updated_at) + VALUES ($1, $2, TRUE, NOW(), NOW()) + `, taskID, userID) + if err != nil { + return fmt.Errorf("failed to create draft: %w", err) + } + log.Printf("Fitbit: created task draft for task_id=%d, auto_complete=true", taskID) + return nil + } + if err != nil { + return fmt.Errorf("failed to get draft: %w", err) + } + + _, err = a.DB.Exec(` + UPDATE task_drafts SET auto_complete = $1, updated_at = NOW() WHERE id = $2 + `, autoComplete, draftID) + if err != nil { + return fmt.Errorf("failed to update draft: %w", err) + } + if !autoComplete { + _, _ = a.DB.Exec("DELETE FROM task_draft_subtasks WHERE task_draft_id = $1", draftID) + } + log.Printf("Fitbit: set task draft auto_complete for task_id=%d to %v", taskID, autoComplete) + return nil +} + // fitbitSyncHandler выполняет ручную синхронизацию данных Fitbit func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "OPTIONS" { diff --git a/play-life-web/package.json b/play-life-web/package.json index 46be506..409c192 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "5.1.1", + "version": "5.1.2", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/FitbitIntegration.jsx b/play-life-web/src/components/FitbitIntegration.jsx index 7e98f8a..a6e8034 100644 --- a/play-life-web/src/components/FitbitIntegration.jsx +++ b/play-life-web/src/components/FitbitIntegration.jsx @@ -437,12 +437,12 @@ function FitbitIntegration({ onNavigate }) { className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100" > - {!loadingTasks && getParentTasks().map(task => ( + {!loadingTasks && tasks.map(task => ( ))} - {editedBindings.steps_goal_task_id && ( + {editedBindings.steps_goal_task_id && stepsGoalSubtasks.length > 0 && (
- {editedBindings.floors_goal_task_id && ( + {editedBindings.floors_goal_task_id && floorsGoalSubtasks.length > 0 && (