Отключены подзадачи для задач-тестов
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m27s

This commit is contained in:
poignatov
2026-01-14 17:50:14 +03:00
parent f9928c6470
commit f13838d91a
5 changed files with 129 additions and 115 deletions

View File

@@ -1 +1 @@
3.13.0
3.14.0

View File

@@ -1,6 +1,6 @@
{
"name": "play-life-web",
"version": "3.13.0",
"version": "3.14.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -508,4 +508,3 @@
color: #6b7280;
font-style: italic;
}

View File

@@ -311,18 +311,23 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
use_progression: r.use_progression
})))
// Загружаем подзадачи
setSubtasks(data.subtasks.map(st => ({
id: st.task.id,
name: st.task.name || '',
reward_message: st.task.reward_message || '',
rewards: st.rewards.map(r => ({
position: r.position,
project_name: r.project_name,
value: String(r.value),
use_progression: r.use_progression
}))
})))
// Загружаем подзадачи (только если задача не является тестом)
if (data.task.config_id) {
// Для задач-тестов не загружаем подзадачи
setSubtasks([])
} else {
setSubtasks(data.subtasks.map(st => ({
id: st.task.id,
name: st.task.name || '',
reward_message: st.task.reward_message || '',
rewards: st.rewards.map(r => ({
position: r.position,
project_name: r.project_name,
value: String(r.value),
use_progression: r.use_progression
}))
})))
}
// Загружаем информацию о связанном желании, если есть
if (data.task.wishlist_id) {
@@ -368,6 +373,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
}
// Тесты не могут иметь прогрессию
setProgressionBase('')
// Тесты не могут иметь подзадачи - очищаем их
setSubtasks([])
} else {
setIsTest(false)
setWordsCount('10')
@@ -381,6 +388,13 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
}
}
// Очистка подзадач при переключении задачи в режим теста
useEffect(() => {
if (isTest && subtasks.length > 0) {
setSubtasks([])
}
}, [isTest])
// Пересчет rewards при изменении reward_message (debounce)
useEffect(() => {
if (debounceTimer.current) {
@@ -644,7 +658,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
value: parseFloat(r.value) || 0,
use_progression: !!(progressionBase && r.use_progression)
})),
subtasks: subtasks.map(st => ({
subtasks: isTest ? [] : subtasks.map(st => ({
id: st.id || undefined,
name: st.name.trim() || null,
reward_message: st.reward_message.trim() || null,
@@ -1058,108 +1072,110 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
)}
</div>
<div className="form-group">
<div className="subtasks-header">
<label>Подзадачи</label>
<button type="button" onClick={handleAddSubtask} className="add-subtask-button" title="Добавить подзадачу">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
{subtasks.map((subtask, index) => (
<div key={index} className="subtask-form-item">
<div className="subtask-header-row">
<input
type="text"
value={subtask.name}
onChange={(e) => handleSubtaskChange(index, 'name', e.target.value)}
placeholder="Название подзадачи"
className="form-input subtask-name-input"
{!isTest && (
<div className="form-group">
<div className="subtasks-header">
<label>Подзадачи</label>
<button type="button" onClick={handleAddSubtask} className="add-subtask-button" title="Добавить подзадачу">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
{subtasks.map((subtask, index) => (
<div key={index} className="subtask-form-item">
<div className="subtask-header-row">
<input
type="text"
value={subtask.name}
onChange={(e) => handleSubtaskChange(index, 'name', e.target.value)}
placeholder="Название подзадачи"
className="form-input subtask-name-input"
/>
<button
type="button"
onClick={() => handleRemoveSubtask(index)}
className="remove-subtask-button"
title="Удалить подзадачу"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
<textarea
value={subtask.reward_message}
onChange={(e) => handleSubtaskRewardMessageChange(index, e.target.value)}
placeholder="Сообщение награды (опционально)"
className="form-textarea"
rows={2}
/>
<button
type="button"
onClick={() => handleRemoveSubtask(index)}
className="remove-subtask-button"
title="Удалить подзадачу"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
<textarea
value={subtask.reward_message}
onChange={(e) => handleSubtaskRewardMessageChange(index, e.target.value)}
placeholder="Сообщение награды (опционально)"
className="form-textarea"
rows={2}
/>
{subtask.rewards && subtask.rewards.length > 0 && (
<div className="subtask-rewards">
{subtask.rewards.map((reward, rIndex) => (
<div key={rIndex} className="reward-item">
<span className="reward-number">{rIndex}</span>
<input
type="text"
value={reward.project_name}
onChange={(e) => {
const newSubtasks = [...subtasks]
newSubtasks[index].rewards[rIndex].project_name = e.target.value
setSubtasks(newSubtasks)
}}
placeholder="Проект"
className="form-input reward-project-input"
list={`subtask-projects-${index}-${rIndex}`}
/>
<datalist id={`subtask-projects-${index}-${rIndex}`}>
{projects.map(p => (
<option key={p.project_id} value={p.project_name} />
))}
</datalist>
<input
type="number"
step="any"
value={reward.value}
onChange={(e) => {
const newSubtasks = [...subtasks]
newSubtasks[index].rewards[rIndex].value = e.target.value
setSubtasks(newSubtasks)
}}
placeholder="Score"
className="form-input reward-score-input"
/>
{progressionBase && (
<button
type="button"
tabIndex={0}
className={`progression-button progression-button-subtask ${reward.use_progression ? 'progression-button-filled' : 'progression-button-outlined'}`}
onMouseDown={(e) => {
e.preventDefault()
{subtask.rewards && subtask.rewards.length > 0 && (
<div className="subtask-rewards">
{subtask.rewards.map((reward, rIndex) => (
<div key={rIndex} className="reward-item">
<span className="reward-number">{rIndex}</span>
<input
type="text"
value={reward.project_name}
onChange={(e) => {
const newSubtasks = [...subtasks]
newSubtasks[index].rewards[rIndex].use_progression = !newSubtasks[index].rewards[rIndex].use_progression
newSubtasks[index].rewards[rIndex].project_name = e.target.value
setSubtasks(newSubtasks)
}}
title={reward.use_progression ? 'Отключить прогрессию' : 'Включить прогрессию'}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</button>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
placeholder="Проект"
className="form-input reward-project-input"
list={`subtask-projects-${index}-${rIndex}`}
/>
<datalist id={`subtask-projects-${index}-${rIndex}`}>
{projects.map(p => (
<option key={p.project_id} value={p.project_name} />
))}
</datalist>
<input
type="number"
step="any"
value={reward.value}
onChange={(e) => {
const newSubtasks = [...subtasks]
newSubtasks[index].rewards[rIndex].value = e.target.value
setSubtasks(newSubtasks)
}}
placeholder="Score"
className="form-input reward-score-input"
/>
{progressionBase && (
<button
type="button"
tabIndex={0}
className={`progression-button progression-button-subtask ${reward.use_progression ? 'progression-button-filled' : 'progression-button-outlined'}`}
onMouseDown={(e) => {
e.preventDefault()
const newSubtasks = [...subtasks]
newSubtasks[index].rewards[rIndex].use_progression = !newSubtasks[index].rewards[rIndex].use_progression
setSubtasks(newSubtasks)
}}
title={reward.use_progression ? 'Отключить прогрессию' : 'Включить прогрессию'}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</button>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
)}
{/* Показываем ошибку валидации только если это ошибка валидации, не ошибка действия */}
{error && (error.includes('обязательно') || error.includes('должны быть заполнены') || error.includes('нельзя одновременно')) && (

View File

@@ -609,4 +609,3 @@
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}