Files
play-life/play-life-web/src/components/AddConfig.jsx

345 lines
11 KiB
React
Raw Normal View History

2025-12-29 20:01:55 +03:00
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