Отключены подзадачи для задач-тестов
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", "name": "play-life-web",
"version": "3.13.0", "version": "3.14.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

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

View File

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

View File

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