Рефакторинг тестов: интеграция с задачами
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 57s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 57s
This commit is contained in:
@@ -6,7 +6,7 @@ import './TaskForm.css'
|
||||
const API_URL = '/api/tasks'
|
||||
const PROJECTS_API_URL = '/projects'
|
||||
|
||||
function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = false }) {
|
||||
const { authFetch } = useAuth()
|
||||
const [name, setName] = useState('')
|
||||
const [progressionBase, setProgressionBase] = useState('')
|
||||
@@ -24,6 +24,12 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [wishlistInfo, setWishlistInfo] = useState(null) // Информация о связанном желании
|
||||
const [currentWishlistId, setCurrentWishlistId] = useState(null) // Текущий wishlist_id задачи
|
||||
// Test-specific state
|
||||
const [isTest, setIsTest] = useState(isTestFromProps)
|
||||
const [wordsCount, setWordsCount] = useState('10')
|
||||
const [maxCards, setMaxCards] = useState('')
|
||||
const [selectedDictionaryIDs, setSelectedDictionaryIDs] = useState([])
|
||||
const [availableDictionaries, setAvailableDictionaries] = useState([])
|
||||
const debounceTimer = useRef(null)
|
||||
|
||||
// Загрузка проектов для автокомплита
|
||||
@@ -42,6 +48,22 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
loadProjects()
|
||||
}, [])
|
||||
|
||||
// Загрузка словарей для тестов
|
||||
useEffect(() => {
|
||||
const loadDictionaries = async () => {
|
||||
try {
|
||||
const response = await authFetch('/api/test-configs-and-dictionaries')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setAvailableDictionaries(Array.isArray(data.dictionaries) ? data.dictionaries : [])
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading dictionaries:', err)
|
||||
}
|
||||
}
|
||||
loadDictionaries()
|
||||
}, [])
|
||||
|
||||
// Функция сброса формы
|
||||
const resetForm = () => {
|
||||
setName('')
|
||||
@@ -54,6 +76,11 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
setSubtasks([])
|
||||
setError('')
|
||||
setLoadingTask(false)
|
||||
// Reset test-specific fields
|
||||
setIsTest(isTestFromProps)
|
||||
setWordsCount('10')
|
||||
setMaxCards('')
|
||||
setSelectedDictionaryIDs([])
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current)
|
||||
debounceTimer.current = null
|
||||
@@ -316,6 +343,28 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
setCurrentWishlistId(null)
|
||||
setWishlistInfo(null)
|
||||
}
|
||||
|
||||
// Загружаем информацию о тесте, если есть config_id
|
||||
if (data.task.config_id) {
|
||||
setIsTest(true)
|
||||
// Данные теста приходят прямо в ответе getTaskDetail
|
||||
if (data.words_count) {
|
||||
setWordsCount(String(data.words_count))
|
||||
}
|
||||
if (data.max_cards) {
|
||||
setMaxCards(String(data.max_cards))
|
||||
}
|
||||
if (data.dictionary_ids && Array.isArray(data.dictionary_ids)) {
|
||||
setSelectedDictionaryIDs(data.dictionary_ids)
|
||||
}
|
||||
// Тесты не могут иметь прогрессию
|
||||
setProgressionBase('')
|
||||
} else {
|
||||
setIsTest(false)
|
||||
setWordsCount('10')
|
||||
setMaxCards('')
|
||||
setSelectedDictionaryIDs([])
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
@@ -551,11 +600,26 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация для тестов
|
||||
if (isTest) {
|
||||
const wordsCountNum = parseInt(wordsCount, 10)
|
||||
if (isNaN(wordsCountNum) || wordsCountNum < 1) {
|
||||
setError('Количество слов должно быть минимум 1')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
if (selectedDictionaryIDs.length === 0) {
|
||||
setError('Выберите хотя бы один словарь')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: name.trim(),
|
||||
reward_message: rewardMessage.trim() || null,
|
||||
// Если задача привязана к желанию, не отправляем progression_base
|
||||
progression_base: isLinkedToWishlist ? null : (progressionBase ? parseFloat(progressionBase) : null),
|
||||
// Тесты и задачи с желанием не могут иметь прогрессию
|
||||
progression_base: (isLinkedToWishlist || isTest) ? null : (progressionBase ? parseFloat(progressionBase) : null),
|
||||
repetition_period: repetitionPeriod,
|
||||
repetition_date: repetitionDate,
|
||||
// При создании: отправляем currentWishlistId если указан (уже число)
|
||||
@@ -580,7 +644,12 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
value: parseFloat(r.value) || 0,
|
||||
use_progression: !!(progressionBase && r.use_progression)
|
||||
}))
|
||||
}))
|
||||
})),
|
||||
// Test-specific fields
|
||||
is_test: isTest,
|
||||
words_count: isTest ? parseInt(wordsCount, 10) : undefined,
|
||||
max_cards: isTest && maxCards ? parseInt(maxCards, 10) : undefined,
|
||||
dictionary_ids: isTest ? selectedDictionaryIDs : undefined
|
||||
}
|
||||
|
||||
const url = taskId ? `${API_URL}/${taskId}` : API_URL
|
||||
@@ -715,26 +784,88 @@ function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => {
|
||||
if (!wishlistInfo) {
|
||||
setProgressionBase(e.target.value)
|
||||
}
|
||||
}}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
disabled={wishlistInfo !== null}
|
||||
/>
|
||||
<small style={{ color: wishlistInfo ? '#e74c3c' : '#666', fontSize: '0.9em' }}>
|
||||
{wishlistInfo ? 'Задачи, привязанные к желанию, не могут иметь прогрессию' : 'Оставьте пустым, если прогрессия не используется'}
|
||||
</small>
|
||||
</div>
|
||||
{!isTest && (
|
||||
<div className="form-group">
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => {
|
||||
if (!wishlistInfo) {
|
||||
setProgressionBase(e.target.value)
|
||||
}
|
||||
}}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
disabled={wishlistInfo !== null}
|
||||
/>
|
||||
<small style={{ color: wishlistInfo ? '#e74c3c' : '#666', fontSize: '0.9em' }}>
|
||||
{wishlistInfo ? 'Задачи, привязанные к желанию, не могут иметь прогрессию' : 'Оставьте пустым, если прогрессия не используется'}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Test-specific fields */}
|
||||
{isTest && (
|
||||
<div className="form-group test-config-section">
|
||||
<label>Настройки теста</label>
|
||||
<div className="test-config-fields">
|
||||
<div className="test-field-group">
|
||||
<label htmlFor="words_count">Количество слов *</label>
|
||||
<input
|
||||
id="words_count"
|
||||
type="number"
|
||||
min="1"
|
||||
value={wordsCount}
|
||||
onChange={(e) => setWordsCount(e.target.value)}
|
||||
className="form-input"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="test-field-group">
|
||||
<label htmlFor="max_cards">Макс. карточек</label>
|
||||
<input
|
||||
id="max_cards"
|
||||
type="number"
|
||||
min="1"
|
||||
value={maxCards}
|
||||
onChange={(e) => setMaxCards(e.target.value)}
|
||||
placeholder="Без ограничения"
|
||||
className="form-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="test-dictionaries-section">
|
||||
<label>Словари *</label>
|
||||
<div className="test-dictionaries-list">
|
||||
{availableDictionaries.map(dict => (
|
||||
<label key={dict.id} className="test-dictionary-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedDictionaryIDs.includes(dict.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedDictionaryIDs([...selectedDictionaryIDs, dict.id])
|
||||
} else {
|
||||
setSelectedDictionaryIDs(selectedDictionaryIDs.filter(id => id !== dict.id))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="test-dictionary-name">{dict.name}</span>
|
||||
<span className="test-dictionary-count">({dict.wordsCount} слов)</span>
|
||||
</label>
|
||||
))}
|
||||
{availableDictionaries.length === 0 && (
|
||||
<div className="test-no-dictionaries">
|
||||
Нет доступных словарей. Создайте словарь в разделе "Словари".
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="repetition_period">Повторения</label>
|
||||
|
||||
Reference in New Issue
Block a user