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 && (
-
+
+ {task.draft_progression_value != null && (
+ {task.draft_progression_value}
+ )}
+
)}