6.27.0: Автовыполнение и прогрессия по-умолчанию
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m32s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -565,7 +565,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
children_task_ids: Array.from(selectedSubtasks)
|
||||
}
|
||||
|
||||
// Если есть прогрессия, отправляем значение (или progression_base, если не введено)
|
||||
// Если есть прогрессия, отправляем значение (или default_progress/progression_base, если не введено)
|
||||
if (taskDetail.task.progression_base != null) {
|
||||
if (progressionValue.trim()) {
|
||||
payload.value = parseFloat(progressionValue)
|
||||
@@ -573,8 +573,8 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
throw new Error('Неверное значение')
|
||||
}
|
||||
} else {
|
||||
// Если прогрессия не введена - используем progression_base
|
||||
payload.value = taskDetail.task.progression_base
|
||||
// Если прогрессия не введена - используем default_progress или progression_base
|
||||
payload.value = taskDetail.task.default_progress ?? taskDetail.task.progression_base
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
children_task_ids: Array.from(selectedSubtasks)
|
||||
}
|
||||
|
||||
// Если есть прогрессия, отправляем значение (или progression_base, если не введено)
|
||||
// Если есть прогрессия, отправляем значение (или default_progress/progression_base, если не введено)
|
||||
if (taskDetail.task.progression_base != null) {
|
||||
if (progressionValue.trim()) {
|
||||
payload.value = parseFloat(progressionValue)
|
||||
@@ -640,8 +640,8 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
throw new Error('Неверное значение')
|
||||
}
|
||||
} else {
|
||||
// Если прогрессия не введена - используем progression_base
|
||||
payload.value = taskDetail.task.progression_base
|
||||
// Если прогрессия не введена - используем default_progress или progression_base
|
||||
payload.value = taskDetail.task.default_progress ?? taskDetail.task.progression_base
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,8 +733,12 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
// Обновляем значение чекбокса при изменении taskDetail
|
||||
useEffect(() => {
|
||||
if (taskDetail && taskDetail.task) {
|
||||
const autoCompleteValue = Boolean(taskDetail.task.auto_complete)
|
||||
console.log('useEffect: Updating completeAtEndOfDay from taskDetail:', autoCompleteValue, 'task.auto_complete:', taskDetail.task.auto_complete)
|
||||
// Если есть драфт (auto_complete или draft_progression_value), используем значение из драфта
|
||||
// Иначе используем default_auto_complete как начальное значение
|
||||
const hasDraft = taskDetail.task.auto_complete || taskDetail.draft_progression_value != null
|
||||
const autoCompleteValue = hasDraft
|
||||
? Boolean(taskDetail.task.auto_complete)
|
||||
: Boolean(taskDetail.task.default_auto_complete)
|
||||
setCompleteAtEndOfDay(autoCompleteValue)
|
||||
} else {
|
||||
setCompleteAtEndOfDay(false)
|
||||
@@ -821,7 +825,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
step="any"
|
||||
value={progressionValue}
|
||||
onChange={(e) => setProgressionValue(e.target.value)}
|
||||
placeholder={task.progression_base?.toString() || ''}
|
||||
placeholder={(task.default_progress ?? task.progression_base)?.toString() || ''}
|
||||
className="progression-input"
|
||||
/>
|
||||
<div className="progression-controls-capsule">
|
||||
@@ -829,7 +833,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
type="button"
|
||||
className="progression-control-btn progression-control-minus"
|
||||
onClick={() => {
|
||||
const base = task.progression_base ?? 1
|
||||
const base = task.default_progress ?? task.progression_base ?? 1
|
||||
const current = progressionValue.trim() ? parseFloat(progressionValue) : base
|
||||
const step = task.progression_base || 1
|
||||
setProgressionValue((current - step).toString())
|
||||
@@ -841,7 +845,7 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
type="button"
|
||||
className="progression-control-btn progression-control-plus"
|
||||
onClick={() => {
|
||||
const base = task.progression_base ?? 1
|
||||
const base = task.default_progress ?? task.progression_base ?? 1
|
||||
const current = progressionValue.trim() ? parseFloat(progressionValue) : base
|
||||
const step = task.progression_base || 1
|
||||
setProgressionValue((current + step).toString())
|
||||
|
||||
@@ -13,6 +13,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId,
|
||||
const { authFetch } = useAuth()
|
||||
const [name, setName] = useState('')
|
||||
const [progressionBase, setProgressionBase] = useState('')
|
||||
const [defaultProgress, setDefaultProgress] = useState('')
|
||||
const [defaultAutoComplete, setDefaultAutoComplete] = useState(false)
|
||||
const [rewardMessage, setRewardMessage] = useState('$name')
|
||||
const [repetitionPeriodValue, setRepetitionPeriodValue] = useState('')
|
||||
const [repetitionPeriodType, setRepetitionPeriodType] = useState('day')
|
||||
@@ -187,6 +189,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId,
|
||||
setName(data.task.name)
|
||||
setRewardMessage(data.task.reward_message || '$name')
|
||||
setProgressionBase(data.task.progression_base ? String(data.task.progression_base) : '')
|
||||
setDefaultProgress(data.task.default_progress ? String(data.task.default_progress) : '')
|
||||
setDefaultAutoComplete(data.task.default_auto_complete || false)
|
||||
setGroupName(data.task.group_name ?? '')
|
||||
|
||||
// Проверяем, является ли задача бесконечной (оба поля = 0)
|
||||
@@ -740,6 +744,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId,
|
||||
reward_message: rewardMessage.trim() || null,
|
||||
// Тесты, закупки и задачи с желанием не могут иметь прогрессию
|
||||
progression_base: (isLinkedToWishlist || isTest || isPurchase) ? null : (progressionBase ? parseFloat(progressionBase) : null),
|
||||
default_progress: (isLinkedToWishlist || isTest || isPurchase) ? null : (defaultProgress ? parseFloat(defaultProgress) : (progressionBase ? parseFloat(progressionBase) : null)),
|
||||
default_auto_complete: defaultAutoComplete,
|
||||
repetition_period: repetitionPeriod,
|
||||
repetition_date: repetitionDate,
|
||||
// При создании: отправляем currentWishlistId если указан (уже число)
|
||||
@@ -1014,15 +1020,28 @@ function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId,
|
||||
<div className="task-type-content">
|
||||
<div className="test-field-group" style={{ marginBottom: '1rem' }}>
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => setProgressionBase(e.target.value)}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => setProgressionBase(e.target.value)}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<input
|
||||
id="default_progress"
|
||||
type="number"
|
||||
step="any"
|
||||
value={defaultProgress}
|
||||
onChange={(e) => setDefaultProgress(e.target.value)}
|
||||
placeholder={progressionBase || 'По-умолчанию'}
|
||||
className="form-input"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</div>
|
||||
<small style={{ color: '#666', fontSize: '0.9em' }}>
|
||||
Оставьте пустым, если прогрессия не используется
|
||||
</small>
|
||||
@@ -1447,6 +1466,22 @@ function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId,
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isTest && !isPurchase && (
|
||||
<div className="complete-at-end-of-day-checkbox" style={{ marginTop: '1rem' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.875rem', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={defaultAutoComplete}
|
||||
onChange={(e) => setDefaultAutoComplete(e.target.checked)}
|
||||
/>
|
||||
Автовыполнение по-умолчанию
|
||||
</label>
|
||||
<small style={{ color: '#666', fontSize: '0.8em', marginLeft: '1.5rem' }}>
|
||||
Задача будет выполняться автоматически в конце каждого дня
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Показываем ошибку валидации только если это ошибка валидации, не ошибка действия */}
|
||||
{error && (error.includes('обязательно') || error.includes('должны быть заполнены') || error.includes('нельзя одновременно')) && (
|
||||
<div className="error-message">{error}</div>
|
||||
|
||||
@@ -746,9 +746,11 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
|
||||
// Сортируем невыполненные задачи: автовыполнение первыми, затем по алфавиту (name ASC), затем по id ASC
|
||||
group.notCompleted.sort((a, b) => {
|
||||
// Задачи с автовыполнением идут первыми
|
||||
if (a.auto_complete && !b.auto_complete) return -1
|
||||
if (!a.auto_complete && b.auto_complete) return 1
|
||||
// Задачи с автовыполнением (включая default_auto_complete) идут первыми
|
||||
const aAuto = a.auto_complete || a.default_auto_complete
|
||||
const bAuto = b.auto_complete || b.default_auto_complete
|
||||
if (aAuto && !bAuto) return -1
|
||||
if (!aAuto && bAuto) return 1
|
||||
|
||||
const nameCompare = (a.name || '').localeCompare(b.name || '')
|
||||
if (nameCompare !== 0) {
|
||||
@@ -759,9 +761,11 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
|
||||
// Сортируем выполненные задачи: автовыполнение первыми, затем бесконечные, затем по next_show_at ASC (ранние в начале), NULL в начале
|
||||
group.completed.sort((a, b) => {
|
||||
// Задачи с автовыполнением идут первыми
|
||||
if (a.auto_complete && !b.auto_complete) return -1
|
||||
if (!a.auto_complete && b.auto_complete) return 1
|
||||
// Задачи с автовыполнением (включая default_auto_complete) идут первыми
|
||||
const aAuto = a.auto_complete || a.default_auto_complete
|
||||
const bAuto = b.auto_complete || b.default_auto_complete
|
||||
if (aAuto && !bAuto) return -1
|
||||
if (!aAuto && bAuto) return 1
|
||||
|
||||
// Проверяем, является ли задача бесконечной
|
||||
const hasZeroPeriodA = a.repetition_period && isZeroPeriod(a.repetition_period)
|
||||
@@ -847,7 +851,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
>
|
||||
<div className="task-item-content">
|
||||
<div
|
||||
className={`task-checkmark ${showDetailOnCheckmark ? 'task-checkmark-detail' : ''} ${task.auto_complete ? 'task-checkmark-auto-complete' : ''}`}
|
||||
className={`task-checkmark ${showDetailOnCheckmark ? 'task-checkmark-detail' : ''} ${(task.auto_complete || task.default_auto_complete) ? 'task-checkmark-auto-complete' : ''}`}
|
||||
onClick={(e) => handleCheckmarkClick(task, e)}
|
||||
title={isTest ? 'Запустить тест' : (isPurchase ? 'Открыть закупки' : (showDetailOnCheckmark ? 'Открыть детали' : 'Выполнить задачу'))}
|
||||
>
|
||||
@@ -915,7 +919,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
<path d="M6 10 L9 13 L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="checkmark-check" />
|
||||
</svg>
|
||||
)}
|
||||
{task.auto_complete && !isTest && !isWishlist && (
|
||||
{(task.auto_complete || task.default_auto_complete) && !isTest && !isWishlist && (
|
||||
<svg
|
||||
className="task-checkmark-auto-complete-icon"
|
||||
width="16"
|
||||
|
||||
Reference in New Issue
Block a user