Initial commit
This commit is contained in:
344
play-life-web/src/components/AddConfig.jsx
Normal file
344
play-life-web/src/components/AddConfig.jsx
Normal file
@@ -0,0 +1,344 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user