5.1.2: Достижение цели Fitbit для задачи без подзадачи
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
This commit is contained in:
@@ -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 {
|
if err := a.saveFitbitSubtaskDraft(userID, int(stepsGoalTaskID.Int64), int(stepsGoalSubtaskID.Int64), goalReached); err != nil {
|
||||||
log.Printf("Error saving steps goal subtask draft: %v", err)
|
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 {
|
if floorsGoalTaskID.Valid && floorsGoalSubtaskID.Valid {
|
||||||
goalReached := floors >= goalFloors
|
goalReached := floors >= goalFloors
|
||||||
if err := a.saveFitbitSubtaskDraft(userID, int(floorsGoalTaskID.Int64), int(floorsGoalSubtaskID.Int64), goalReached); err != nil {
|
if err := a.saveFitbitSubtaskDraft(userID, int(floorsGoalTaskID.Int64), int(floorsGoalSubtaskID.Int64), goalReached); err != nil {
|
||||||
log.Printf("Error saving floors goal subtask draft: %v", err)
|
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",
|
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
|
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
|
// fitbitSyncHandler выполняет ручную синхронизацию данных Fitbit
|
||||||
func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) {
|
func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -437,12 +437,12 @@ function FitbitIntegration({ onNavigate }) {
|
|||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
|
||||||
>
|
>
|
||||||
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
|
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
|
||||||
{!loadingTasks && getParentTasks().map(task => (
|
{!loadingTasks && tasks.map(task => (
|
||||||
<option key={task.id} value={task.id}>{task.name}</option>
|
<option key={task.id} value={task.id}>{task.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{editedBindings.steps_goal_task_id && (
|
{editedBindings.steps_goal_task_id && stepsGoalSubtasks.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-gray-600 mb-1">Подзадача</label>
|
<label className="block text-sm text-gray-600 mb-1">Подзадача</label>
|
||||||
<select
|
<select
|
||||||
@@ -531,12 +531,12 @@ function FitbitIntegration({ onNavigate }) {
|
|||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
|
||||||
>
|
>
|
||||||
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
|
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
|
||||||
{!loadingTasks && getParentTasks().map(task => (
|
{!loadingTasks && tasks.map(task => (
|
||||||
<option key={task.id} value={task.id}>{task.name}</option>
|
<option key={task.id} value={task.id}>{task.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{editedBindings.floors_goal_task_id && (
|
{editedBindings.floors_goal_task_id && floorsGoalSubtasks.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-gray-600 mb-1">Подзадача</label>
|
<label className="block text-sm text-gray-600 mb-1">Подзадача</label>
|
||||||
<select
|
<select
|
||||||
|
|||||||
Reference in New Issue
Block a user