5.8.0: Окно переноса и логика next_show_at
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m22s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m22s
This commit is contained in:
@@ -7773,9 +7773,9 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
-- Для невыполненных: сортируем по completed DESC (больше завершений выше), затем по id ASC (раньше добавленные выше)
|
-- Для невыполненных: сортируем по 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.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,
|
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
|
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
|
ELSE '1970-01-01'::timestamp with time zone
|
||||||
END
|
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}
|
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, wishlistIDValue, rewardPolicyValue, req.GroupName}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 = `
|
insertSQL = `
|
||||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy, group_name)
|
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, 0, FALSE, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, NULL, NULL, $5, 0, FALSE, $6, $7, $8)
|
||||||
RETURNING id
|
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)
|
err = tx.QueryRow(insertSQL, insertArgs...).Scan(&taskID)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "5.7.0",
|
"version": "5.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -319,8 +319,8 @@
|
|||||||
.task-postpone-modal {
|
.task-postpone-modal {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
max-width: 400px;
|
width: fit-content;
|
||||||
width: 90%;
|
max-width: 90%;
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +491,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 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 {
|
.task-postpone-quick-button {
|
||||||
@@ -503,6 +513,8 @@
|
|||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-quick-button:hover:not(:disabled) {
|
.task-postpone-quick-button:hover:not(:disabled) {
|
||||||
|
|||||||
@@ -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) => {
|
const toggleCompletedExpanded = (projectName) => {
|
||||||
setExpandedCompleted(prev => ({
|
setExpandedCompleted(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -622,7 +658,8 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
nextShowDate.setHours(0, 0, 0, 0)
|
nextShowDate.setHours(0, 0, 0, 0)
|
||||||
isCompleted = nextShowDate.getTime() > today.getTime()
|
isCompleted = nextShowDate.getTime() > today.getTime()
|
||||||
} else {
|
} else {
|
||||||
isCompleted = false
|
// Задачи без даты (next_show_at = null) идут в выполненные
|
||||||
|
isCompleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
groupKeys.forEach(groupKey => {
|
groupKeys.forEach(groupKey => {
|
||||||
@@ -670,10 +707,10 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
if (!isInfiniteA && isInfiniteB) return 1
|
if (!isInfiniteA && isInfiniteB) return 1
|
||||||
if (isInfiniteA && isInfiniteB) return 0
|
if (isInfiniteA && isInfiniteB) return 0
|
||||||
|
|
||||||
// Для остальных: NULL значения идут первыми
|
// Для остальных: NULL значения идут последними
|
||||||
if (!a.next_show_at && !b.next_show_at) return 0
|
if (!a.next_show_at && !b.next_show_at) return 0
|
||||||
if (!a.next_show_at) return -1
|
if (!a.next_show_at) return 1
|
||||||
if (!b.next_show_at) return 1
|
if (!b.next_show_at) return -1
|
||||||
|
|
||||||
// Сравниваем даты
|
// Сравниваем даты
|
||||||
const dateA = new Date(a.next_show_at).getTime()
|
const dateA = new Date(a.next_show_at).getTime()
|
||||||
@@ -1182,6 +1219,15 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
По плану
|
По плану
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{selectedTaskForPostpone?.next_show_at && (
|
||||||
|
<button
|
||||||
|
onClick={handleWithoutDateClick}
|
||||||
|
className="task-postpone-quick-button"
|
||||||
|
disabled={isPostponing}
|
||||||
|
>
|
||||||
|
Без даты
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user