5.10.0: Капсула прогрессии в списке задач
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s
This commit is contained in:
@@ -343,6 +343,7 @@ type Task struct {
|
|||||||
SubtasksCount int `json:"subtasks_count"`
|
SubtasksCount int `json:"subtasks_count"`
|
||||||
HasProgression bool `json:"has_progression"`
|
HasProgression bool `json:"has_progression"`
|
||||||
AutoComplete bool `json:"auto_complete"`
|
AutoComplete bool `json:"auto_complete"`
|
||||||
|
DraftProgressionValue *float64 `json:"draft_progression_value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Reward struct {
|
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),
|
WHERE st.parent_task_id = t.id AND st.deleted = FALSE),
|
||||||
ARRAY[]::text[]
|
ARRAY[]::text[]
|
||||||
) as subtask_project_names,
|
) 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
|
FROM tasks t
|
||||||
LEFT JOIN task_drafts td ON td.task_id = t.id AND td.user_id = $1
|
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
|
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 projectNames pq.StringArray
|
||||||
var subtaskProjectNames pq.StringArray
|
var subtaskProjectNames pq.StringArray
|
||||||
var autoComplete bool
|
var autoComplete bool
|
||||||
|
var draftProgressionValue sql.NullFloat64
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&task.ID,
|
&task.ID,
|
||||||
@@ -7823,6 +7826,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
&projectNames,
|
&projectNames,
|
||||||
&subtaskProjectNames,
|
&subtaskProjectNames,
|
||||||
&autoComplete,
|
&autoComplete,
|
||||||
|
&draftProgressionValue,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error scanning task: %v", err)
|
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.GroupName = &groupNameVal
|
||||||
}
|
}
|
||||||
task.AutoComplete = autoComplete
|
task.AutoComplete = autoComplete
|
||||||
|
if draftProgressionValue.Valid {
|
||||||
|
task.DraftProgressionValue = &draftProgressionValue.Float64
|
||||||
|
}
|
||||||
|
|
||||||
// Объединяем проекты из основной задачи и подзадач
|
// Объединяем проекты из основной задачи и подзадач
|
||||||
allProjects := make(map[string]bool)
|
allProjects := make(map[string]bool)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "5.9.0",
|
"version": "5.10.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
|||||||
children_task_ids: Array.from(selectedSubtasks)
|
children_task_ids: Array.from(selectedSubtasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если есть прогрессия, отправляем значение (или progression_base, если не введено)
|
// Если есть прогрессия, отправляем значение (или null, если не введено)
|
||||||
if (taskDetail.task.progression_base != null) {
|
if (taskDetail.task.progression_base != null) {
|
||||||
if (progressionValue.trim()) {
|
if (progressionValue.trim()) {
|
||||||
const parsedValue = parseFloat(progressionValue)
|
const parsedValue = parseFloat(progressionValue)
|
||||||
@@ -486,8 +486,8 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
|||||||
}
|
}
|
||||||
payload.progression_value = parsedValue
|
payload.progression_value = parsedValue
|
||||||
} else {
|
} else {
|
||||||
// Если прогрессия не введена - используем progression_base
|
// Если прогрессия не введена - отправляем null
|
||||||
payload.progression_value = taskDetail.task.progression_base
|
payload.progression_value = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если нет progression_base, но пользователь ввел значение - отправляем его
|
// Если нет progression_base, но пользователь ввел значение - отправляем его
|
||||||
|
|||||||
@@ -256,11 +256,41 @@
|
|||||||
margin-left: 0.25rem;
|
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 {
|
.task-progression-icon {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-progression-value {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
min-width: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.task-infinite-icon {
|
.task-infinite-icon {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
const [isPostponing, setIsPostponing] = useState(false)
|
const [isPostponing, setIsPostponing] = useState(false)
|
||||||
const [toast, setToast] = useState(null)
|
const [toast, setToast] = useState(null)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [savingProgressionTaskId, setSavingProgressionTaskId] = useState(null)
|
||||||
// Режим группировки: 'project' (по проекту - по умолчанию) или 'group' (по группе)
|
// Режим группировки: 'project' (по проекту - по умолчанию) или 'group' (по группе)
|
||||||
const [groupingMode, setGroupingMode] = useState(() => {
|
const [groupingMode, setGroupingMode] = useState(() => {
|
||||||
// Восстанавливаем из localStorage, по умолчанию 'project'
|
// Восстанавливаем из 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)
|
// Получаем все проекты из задачи (теперь они приходят в task.project_names)
|
||||||
const getTaskProjects = (task) => {
|
const getTaskProjects = (task) => {
|
||||||
if (task.project_names && Array.isArray(task.project_names)) {
|
if (task.project_names && Array.isArray(task.project_names)) {
|
||||||
@@ -888,21 +928,34 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
{hasProgression && (
|
{hasProgression && (
|
||||||
|
<span
|
||||||
|
className={`task-progression-capsule ${savingProgressionTaskId === task.id ? 'task-progression-capsule--saving' : ''}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (savingProgressionTaskId !== task.id) {
|
||||||
|
handleProgressionChange(task, task.progression_base)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title="Задача с прогрессией"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
className="task-progression-icon"
|
className="task-progression-icon"
|
||||||
width="16"
|
width="14"
|
||||||
height="16"
|
height="14"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
title="Задача с прогрессией"
|
|
||||||
>
|
>
|
||||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
||||||
<polyline points="17 6 23 6 23 12"></polyline>
|
<polyline points="17 6 23 6 23 12"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
|
{task.draft_progression_value != null && (
|
||||||
|
<span className="task-progression-value">{task.draft_progression_value}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user