diff --git a/VERSION b/VERSION index b3d91f9..509b0b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.9.0 +5.10.0 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 75c8304..aef7415 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -338,11 +338,12 @@ type Task struct { RewardPolicy *string `json:"reward_policy,omitempty"` // "personal" или "general" для задач, связанных с желаниями Position *int `json:"position,omitempty"` // Position for subtasks // Дополнительные поля для списка задач (без omitempty чтобы всегда передавались) - ProjectNames []string `json:"project_names"` - GroupName *string `json:"group_name,omitempty"` // Название группы задачи - SubtasksCount int `json:"subtasks_count"` - HasProgression bool `json:"has_progression"` - AutoComplete bool `json:"auto_complete"` + ProjectNames []string `json:"project_names"` + GroupName *string `json:"group_name,omitempty"` // Название группы задачи + SubtasksCount int `json:"subtasks_count"` + HasProgression bool `json:"has_progression"` + AutoComplete bool `json:"auto_complete"` + DraftProgressionValue *float64 `json:"draft_progression_value,omitempty"` } type Reward struct { @@ -7765,7 +7766,8 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) { WHERE st.parent_task_id = t.id AND st.deleted = FALSE), ARRAY[]::text[] ) as subtask_project_names, - COALESCE(td.auto_complete, FALSE) as auto_complete + COALESCE(td.auto_complete, FALSE) as auto_complete, + td.progression_value as draft_progression_value FROM tasks t LEFT JOIN task_drafts td ON td.task_id = t.id AND td.user_id = $1 WHERE t.user_id = $1 AND t.parent_task_id IS NULL AND t.deleted = FALSE @@ -7805,6 +7807,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) { var projectNames pq.StringArray var subtaskProjectNames pq.StringArray var autoComplete bool + var draftProgressionValue sql.NullFloat64 err := rows.Scan( &task.ID, @@ -7823,6 +7826,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) { &projectNames, &subtaskProjectNames, &autoComplete, + &draftProgressionValue, ) if err != nil { log.Printf("Error scanning task: %v", err) @@ -7863,6 +7867,9 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) { task.GroupName = &groupNameVal } task.AutoComplete = autoComplete + if draftProgressionValue.Valid { + task.DraftProgressionValue = &draftProgressionValue.Float64 + } // Объединяем проекты из основной задачи и подзадач allProjects := make(map[string]bool) diff --git a/play-life-web/package.json b/play-life-web/package.json index 90dcab4..10db1d6 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "5.9.0", + "version": "5.10.0", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/TaskDetail.jsx b/play-life-web/src/components/TaskDetail.jsx index 55a3527..0ed9230 100644 --- a/play-life-web/src/components/TaskDetail.jsx +++ b/play-life-web/src/components/TaskDetail.jsx @@ -477,7 +477,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate }) children_task_ids: Array.from(selectedSubtasks) } - // Если есть прогрессия, отправляем значение (или progression_base, если не введено) + // Если есть прогрессия, отправляем значение (или null, если не введено) if (taskDetail.task.progression_base != null) { if (progressionValue.trim()) { const parsedValue = parseFloat(progressionValue) @@ -486,8 +486,8 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate }) } payload.progression_value = parsedValue } else { - // Если прогрессия не введена - используем progression_base - payload.progression_value = taskDetail.task.progression_base + // Если прогрессия не введена - отправляем null + payload.progression_value = null } } else { // Если нет progression_base, но пользователь ввел значение - отправляем его diff --git a/play-life-web/src/components/TaskList.css b/play-life-web/src/components/TaskList.css index 988e8f2..4398224 100644 --- a/play-life-web/src/components/TaskList.css +++ b/play-life-web/src/components/TaskList.css @@ -256,11 +256,41 @@ margin-left: 0.25rem; } +.task-progression-capsule { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + background: #f3f4f6; + border-radius: 9999px; + padding: 0.15rem 0.5rem; + cursor: pointer; + transition: background 0.2s; + min-height: 1.5rem; +} + +.task-progression-capsule:hover { + background: #e5e7eb; +} + +.task-progression-capsule--saving { + opacity: 0.6; + cursor: not-allowed; +} + .task-progression-icon { color: #9ca3af; flex-shrink: 0; } +.task-progression-value { + font-size: 0.8rem; + font-weight: 500; + color: #374151; + min-width: 1rem; + text-align: center; +} + .task-infinite-icon { color: #9ca3af; flex-shrink: 0; diff --git a/play-life-web/src/components/TaskList.jsx b/play-life-web/src/components/TaskList.jsx index e746a7f..0ef100a 100644 --- a/play-life-web/src/components/TaskList.jsx +++ b/play-life-web/src/components/TaskList.jsx @@ -23,6 +23,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry const [isPostponing, setIsPostponing] = useState(false) const [toast, setToast] = useState(null) const [searchQuery, setSearchQuery] = useState('') + const [savingProgressionTaskId, setSavingProgressionTaskId] = useState(null) // Режим группировки: 'project' (по проекту - по умолчанию) или 'group' (по группе) const [groupingMode, setGroupingMode] = useState(() => { // Восстанавливаем из localStorage, по умолчанию 'project' @@ -551,6 +552,45 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry })) } + const handleProgressionChange = async (task, delta) => { + if (savingProgressionTaskId === task.id) return + + const currentValue = task.draft_progression_value ?? 0 + const newValue = currentValue + delta + + setSavingProgressionTaskId(task.id) + try { + const response = await authFetch(`${API_URL}/${task.id}/draft`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + progression_value: newValue, + auto_complete: false, + children_task_ids: [] + }), + }) + + if (!response.ok) { + throw new Error('Ошибка при сохранении прогрессии') + } + + setTasks(prevTasks => + prevTasks.map(t => + t.id === task.id + ? { ...t, draft_progression_value: newValue } + : t + ) + ) + } catch (err) { + console.error('Error saving progression:', err) + setToast({ message: err.message || 'Ошибка при сохранении прогрессии', type: 'error' }) + } finally { + setSavingProgressionTaskId(null) + } + } + // Получаем все проекты из задачи (теперь они приходят в task.project_names) const getTaskProjects = (task) => { if (task.project_names && Array.isArray(task.project_names)) { @@ -888,21 +928,34 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry )} {hasProgression && ( - { + e.stopPropagation() + if (savingProgressionTaskId !== task.id) { + handleProgressionChange(task, task.progression_base) + } + }} title="Задача с прогрессией" > - - - + + + + + {task.draft_progression_value != null && ( + {task.draft_progression_value} + )} + )}