diff --git a/VERSION b/VERSION index 42cdd0b..11d9efa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.7.0 +5.8.0 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 8f4ba34..06d6b73 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -7773,9 +7773,9 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) { -- Для невыполненных: сортируем по completed DESC (больше завершений выше), затем по id ASC (раньше добавленные выше) CASE WHEN t.last_completed_at IS NULL OR t.last_completed_at::date < CURRENT_DATE THEN -t.completed ELSE 0 END, CASE WHEN t.last_completed_at IS NULL OR t.last_completed_at::date < CURRENT_DATE THEN t.id ELSE 0 END, - -- Для выполненных: сортируем по next_show_at ASC (ранние в начале), NULL значения в начале через COALESCE + -- Для выполненных: сортируем по next_show_at ASC (ранние в начале), NULL значения в конце через COALESCE CASE WHEN t.last_completed_at IS NOT NULL AND t.last_completed_at::date >= CURRENT_DATE - THEN COALESCE(t.next_show_at, '1970-01-01'::timestamp with time zone) + THEN COALESCE(t.next_show_at, '9999-12-31'::timestamp with time zone) ELSE '1970-01-01'::timestamp with time zone END ` @@ -8566,12 +8566,21 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) { insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, wishlistIDValue, rewardPolicyValue, req.GroupName} } } else { + // Получаем часовой пояс для задач без повторения + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, err := time.LoadLocation(timezoneStr) + if err != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC instead.", timezoneStr, err) + loc = time.UTC + } + now := time.Now().In(loc) + insertSQL = ` - INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy, group_name) - VALUES ($1, $2, $3, $4, NULL, NULL, 0, FALSE, $5, $6, $7) + INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, next_show_at, completed, deleted, wishlist_id, reward_policy, group_name) + VALUES ($1, $2, $3, $4, NULL, NULL, $5, 0, FALSE, $6, $7, $8) RETURNING id ` - insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, wishlistIDValue, rewardPolicyValue, req.GroupName} + insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, now, wishlistIDValue, rewardPolicyValue, req.GroupName} } err = tx.QueryRow(insertSQL, insertArgs...).Scan(&taskID) diff --git a/play-life-web/package.json b/play-life-web/package.json index b391f6b..81e1285 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "5.7.0", + "version": "5.8.0", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/TaskList.css b/play-life-web/src/components/TaskList.css index 5ff35a5..988e8f2 100644 --- a/play-life-web/src/components/TaskList.css +++ b/play-life-web/src/components/TaskList.css @@ -319,8 +319,8 @@ .task-postpone-modal { background: white; border-radius: 0.5rem; - max-width: 400px; - width: 90%; + width: fit-content; + max-width: 90%; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); } @@ -491,6 +491,16 @@ display: flex; gap: 0.5rem; margin-top: 0.5rem; + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + width: 100%; +} + +.task-postpone-quick-buttons::-webkit-scrollbar { + display: none; } .task-postpone-quick-button { @@ -503,6 +513,8 @@ background: white; cursor: pointer; transition: all 0.2s; + flex-shrink: 0; + white-space: nowrap; } .task-postpone-quick-button:hover:not(:disabled) { diff --git a/play-life-web/src/components/TaskList.jsx b/play-life-web/src/components/TaskList.jsx index 52dd495..e746a7f 100644 --- a/play-life-web/src/components/TaskList.jsx +++ b/play-life-web/src/components/TaskList.jsx @@ -508,6 +508,42 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry } } + const handleWithoutDateClick = async () => { + if (!selectedTaskForPostpone) return + + setIsPostponing(true) + try { + const response = await authFetch(`${API_URL}/${selectedTaskForPostpone.id}/postpone`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ next_show_at: null }), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.message || 'Ошибка при переносе задачи') + } + + if (onRefresh) { + onRefresh() + } + + if (historyPushedForPostponeRef.current) { + window.history.back() + } else { + setSelectedTaskForPostpone(null) + setPostponeDate('') + } + } catch (err) { + console.error('Error postponing task:', err) + setToast({ message: err.message || 'Ошибка при переносе задачи', type: 'error' }) + } finally { + setIsPostponing(false) + } + } + const toggleCompletedExpanded = (projectName) => { setExpandedCompleted(prev => ({ ...prev, @@ -622,7 +658,8 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry nextShowDate.setHours(0, 0, 0, 0) isCompleted = nextShowDate.getTime() > today.getTime() } else { - isCompleted = false + // Задачи без даты (next_show_at = null) идут в выполненные + isCompleted = true } groupKeys.forEach(groupKey => { @@ -670,10 +707,10 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry if (!isInfiniteA && isInfiniteB) return 1 if (isInfiniteA && isInfiniteB) return 0 - // Для остальных: NULL значения идут первыми + // Для остальных: NULL значения идут последними if (!a.next_show_at && !b.next_show_at) return 0 - if (!a.next_show_at) return -1 - if (!b.next_show_at) return 1 + if (!a.next_show_at) return 1 + if (!b.next_show_at) return -1 // Сравниваем даты const dateA = new Date(a.next_show_at).getTime() @@ -1182,6 +1219,15 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry По плану )} + {selectedTaskForPostpone?.next_show_at && ( + + )}