345 lines
11 KiB
JavaScript
345 lines
11 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
||
import './AddConfig.css'
|
||
|
||
const API_URL = '/api'
|
||
|
||
function AddConfig({ onNavigate, editingConfig: initialEditingConfig }) {
|
||
const [name, setName] = useState('')
|
||
const [tryMessage, setTryMessage] = useState('')
|
||
const [wordsCount, setWordsCount] = useState('10')
|
||
const [maxCards, setMaxCards] = useState('')
|
||
const [message, setMessage] = useState('')
|
||
const [loading, setLoading] = useState(false)
|
||
const [dictionaries, setDictionaries] = useState([])
|
||
const [selectedDictionaryIds, setSelectedDictionaryIds] = useState([])
|
||
const [loadingDictionaries, setLoadingDictionaries] = useState(false)
|
||
|
||
// Load dictionaries
|
||
useEffect(() => {
|
||
const loadDictionaries = async () => {
|
||
setLoadingDictionaries(true)
|
||
try {
|
||
const response = await fetch(`${API_URL}/test-configs-and-dictionaries`)
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка при загрузке словарей')
|
||
}
|
||
const data = await response.json()
|
||
setDictionaries(Array.isArray(data.dictionaries) ? data.dictionaries : [])
|
||
} catch (err) {
|
||
console.error('Failed to load dictionaries:', err)
|
||
} finally {
|
||
setLoadingDictionaries(false)
|
||
}
|
||
}
|
||
loadDictionaries()
|
||
}, [])
|
||
|
||
// Load selected dictionaries when editing
|
||
useEffect(() => {
|
||
const loadSelectedDictionaries = async () => {
|
||
if (initialEditingConfig?.id) {
|
||
try {
|
||
const response = await fetch(`${API_URL}/configs/${initialEditingConfig.id}/dictionaries`)
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
setSelectedDictionaryIds(Array.isArray(data.dictionary_ids) ? data.dictionary_ids : [])
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to load selected dictionaries:', err)
|
||
}
|
||
} else {
|
||
setSelectedDictionaryIds([])
|
||
}
|
||
}
|
||
loadSelectedDictionaries()
|
||
}, [initialEditingConfig])
|
||
|
||
useEffect(() => {
|
||
if (initialEditingConfig) {
|
||
setName(initialEditingConfig.name)
|
||
setTryMessage(initialEditingConfig.try_message)
|
||
setWordsCount(String(initialEditingConfig.words_count))
|
||
setMaxCards(initialEditingConfig.max_cards ? String(initialEditingConfig.max_cards) : '')
|
||
} else {
|
||
// Сбрасываем состояние при открытии в режиме добавления
|
||
setName('')
|
||
setTryMessage('')
|
||
setWordsCount('10')
|
||
setMaxCards('')
|
||
setMessage('')
|
||
setSelectedDictionaryIds([])
|
||
}
|
||
}, [initialEditingConfig])
|
||
|
||
// Сбрасываем состояние при размонтировании компонента
|
||
useEffect(() => {
|
||
return () => {
|
||
setName('')
|
||
setTryMessage('')
|
||
setWordsCount('10')
|
||
setMaxCards('')
|
||
setMessage('')
|
||
setLoading(false)
|
||
}
|
||
}, [])
|
||
|
||
const handleSubmit = async (e) => {
|
||
e.preventDefault()
|
||
setMessage('')
|
||
setLoading(true)
|
||
|
||
if (!name.trim()) {
|
||
setMessage('Имя обязательно для заполнения.')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
try {
|
||
const url = initialEditingConfig
|
||
? `${API_URL}/configs/${initialEditingConfig.id}`
|
||
: `${API_URL}/configs`
|
||
const method = initialEditingConfig ? 'PUT' : 'POST'
|
||
|
||
const response = await fetch(url, {
|
||
method: method,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
name: name.trim(),
|
||
try_message: tryMessage.trim() || '',
|
||
words_count: wordsCount === '' ? 0 : parseInt(wordsCount) || 0,
|
||
max_cards: maxCards === '' ? null : parseInt(maxCards) || null,
|
||
dictionary_ids: selectedDictionaryIds.length > 0 ? selectedDictionaryIds : undefined,
|
||
}),
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}))
|
||
const errorMessage = errorData.message || response.statusText || `Ошибка при ${initialEditingConfig ? 'обновлении' : 'создании'} конфигурации`
|
||
throw new Error(errorMessage)
|
||
}
|
||
|
||
if (!initialEditingConfig) {
|
||
setName('')
|
||
setTryMessage('')
|
||
setWordsCount('10')
|
||
setMaxCards('')
|
||
}
|
||
|
||
// Navigate back immediately
|
||
onNavigate?.('test-config')
|
||
} catch (error) {
|
||
setMessage(`Ошибка: ${error.message}`)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const getNumericValue = () => {
|
||
return wordsCount === '' ? 0 : parseInt(wordsCount) || 0
|
||
}
|
||
|
||
const getMaxCardsNumericValue = () => {
|
||
return maxCards === '' ? 0 : parseInt(maxCards) || 0
|
||
}
|
||
|
||
const handleDecrease = () => {
|
||
const numValue = getNumericValue()
|
||
if (numValue > 0) {
|
||
setWordsCount(String(numValue - 1))
|
||
}
|
||
}
|
||
|
||
const handleIncrease = () => {
|
||
const numValue = getNumericValue()
|
||
setWordsCount(String(numValue + 1))
|
||
}
|
||
|
||
const handleMaxCardsDecrease = () => {
|
||
const numValue = getMaxCardsNumericValue()
|
||
if (numValue > 0) {
|
||
setMaxCards(String(numValue - 1))
|
||
} else {
|
||
setMaxCards('')
|
||
}
|
||
}
|
||
|
||
const handleMaxCardsIncrease = () => {
|
||
const numValue = getMaxCardsNumericValue()
|
||
const newValue = numValue + 1
|
||
setMaxCards(String(newValue))
|
||
}
|
||
|
||
const handleClose = () => {
|
||
// Сбрасываем состояние при закрытии
|
||
setName('')
|
||
setTryMessage('')
|
||
setWordsCount('10')
|
||
setMaxCards('')
|
||
setMessage('')
|
||
onNavigate?.('test-config')
|
||
}
|
||
|
||
return (
|
||
<div className="add-config">
|
||
<button className="close-x-button" onClick={handleClose}>
|
||
✕
|
||
</button>
|
||
<h2>Конфигурация теста</h2>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
<div className="form-group">
|
||
<label htmlFor="name">Имя</label>
|
||
<input
|
||
id="name"
|
||
type="text"
|
||
className="form-input"
|
||
value={name}
|
||
onChange={(e) => setName(e.target.value)}
|
||
placeholder="Название конфига"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="tryMessage">Сообщение (необязательно)</label>
|
||
<textarea
|
||
id="tryMessage"
|
||
className="form-textarea"
|
||
value={tryMessage}
|
||
onChange={(e) => setTryMessage(e.target.value)}
|
||
placeholder="Сообщение которое будет отправлено в play-life при прохождении теста"
|
||
rows={4}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="wordsCount">Кол-во слов</label>
|
||
<div className="stepper-container">
|
||
<button
|
||
type="button"
|
||
className="stepper-button"
|
||
onClick={handleDecrease}
|
||
disabled={getNumericValue() <= 0}
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
id="wordsCount"
|
||
type="number"
|
||
className="stepper-input"
|
||
value={wordsCount}
|
||
onChange={(e) => {
|
||
const inputValue = e.target.value
|
||
if (inputValue === '') {
|
||
setWordsCount('')
|
||
} else {
|
||
const numValue = parseInt(inputValue)
|
||
if (!isNaN(numValue) && numValue >= 0) {
|
||
setWordsCount(inputValue)
|
||
}
|
||
}
|
||
}}
|
||
min="0"
|
||
required
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="stepper-button"
|
||
onClick={handleIncrease}
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="maxCards">Макс. кол-во карточек (необязательно)</label>
|
||
<div className="stepper-container">
|
||
<button
|
||
type="button"
|
||
className="stepper-button"
|
||
onClick={handleMaxCardsDecrease}
|
||
disabled={getMaxCardsNumericValue() <= 0}
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
id="maxCards"
|
||
type="number"
|
||
className="stepper-input"
|
||
value={maxCards}
|
||
onChange={(e) => {
|
||
const inputValue = e.target.value
|
||
if (inputValue === '') {
|
||
setMaxCards('')
|
||
} else {
|
||
const numValue = parseInt(inputValue)
|
||
if (!isNaN(numValue) && numValue >= 0) {
|
||
setMaxCards(inputValue)
|
||
}
|
||
}
|
||
}}
|
||
min="0"
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="stepper-button"
|
||
onClick={handleMaxCardsIncrease}
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="dictionaries">Словари (необязательно)</label>
|
||
<div className="dictionaries-hint">
|
||
Если не выбрано ни одного словаря, будут использоваться все словари
|
||
</div>
|
||
{loadingDictionaries ? (
|
||
<div>Загрузка словарей...</div>
|
||
) : (
|
||
<div className="dictionaries-checkbox-list">
|
||
{dictionaries.map((dict) => (
|
||
<label key={dict.id} className="dictionary-checkbox-label">
|
||
<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>{dict.name} ({dict.wordsCount})</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
className="submit-button"
|
||
disabled={loading || !name.trim() || getNumericValue() === 0}
|
||
>
|
||
{loading ? (initialEditingConfig ? 'Обновление...' : 'Создание...') : (initialEditingConfig ? 'Обновить конфигурацию' : 'Создать конфигурацию')}
|
||
</button>
|
||
</form>
|
||
|
||
{message && (
|
||
<div className={`message ${message.includes('Ошибка') ? 'error' : 'success'}`}>
|
||
{message}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AddConfig
|
||
|