Initial commit
This commit is contained in:
246
play-life-web/src/components/WordList.jsx
Normal file
246
play-life-web/src/components/WordList.jsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import './WordList.css'
|
||||
|
||||
const API_URL = '/api'
|
||||
|
||||
function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger = 0 }) {
|
||||
const [words, setWords] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [dictionary, setDictionary] = useState(null)
|
||||
const [dictionaryName, setDictionaryName] = useState('')
|
||||
const [originalDictionaryName, setOriginalDictionaryName] = useState('')
|
||||
const [isSavingName, setIsSavingName] = useState(false)
|
||||
const [currentDictionaryId, setCurrentDictionaryId] = useState(dictionaryId)
|
||||
const [isNewDict, setIsNewDict] = useState(isNewDictionary)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentDictionaryId(dictionaryId)
|
||||
setIsNewDict(isNewDictionary)
|
||||
|
||||
if (isNewDictionary) {
|
||||
setLoading(false)
|
||||
setDictionary(null)
|
||||
setDictionaryName('')
|
||||
setOriginalDictionaryName('')
|
||||
setWords([])
|
||||
} else if (dictionaryId !== undefined && dictionaryId !== null) {
|
||||
fetchDictionary()
|
||||
fetchWords()
|
||||
} else {
|
||||
setLoading(false)
|
||||
setWords([])
|
||||
}
|
||||
}, [dictionaryId, isNewDictionary, refreshTrigger])
|
||||
|
||||
const fetchDictionary = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/dictionaries`)
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при загрузке словарей')
|
||||
}
|
||||
const dictionaries = await response.json()
|
||||
const dict = dictionaries.find(d => d.id === dictionaryId)
|
||||
if (dict) {
|
||||
setDictionary(dict)
|
||||
setDictionaryName(dict.name)
|
||||
setOriginalDictionaryName(dict.name)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching dictionary:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchWords = async () => {
|
||||
if (isNewDictionary || dictionaryId === undefined || dictionaryId === null) {
|
||||
setWords([])
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
await fetchWordsForDictionary(dictionaryId)
|
||||
}
|
||||
|
||||
const fetchWordsForDictionary = async (dictId) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const url = `${API_URL}/words?dictionary_id=${dictId}`
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при загрузке слов')
|
||||
}
|
||||
const data = await response.json()
|
||||
setWords(Array.isArray(data) ? data : [])
|
||||
setError('')
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
setWords([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNameChange = (e) => {
|
||||
setDictionaryName(e.target.value)
|
||||
}
|
||||
|
||||
const handleNameSave = async () => {
|
||||
if (!dictionaryName.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsSavingName(true)
|
||||
try {
|
||||
if (isNewDictionary) {
|
||||
// Create new dictionary
|
||||
const response = await fetch(`${API_URL}/dictionaries`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ name: dictionaryName.trim() }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при создании словаря')
|
||||
}
|
||||
|
||||
const newDict = await response.json()
|
||||
const newDictionaryId = newDict.id
|
||||
|
||||
// Update local state immediately
|
||||
setOriginalDictionaryName(newDict.name)
|
||||
setDictionaryName(newDict.name)
|
||||
setDictionary(newDict)
|
||||
setCurrentDictionaryId(newDictionaryId)
|
||||
setIsNewDict(false)
|
||||
|
||||
// Fetch words for the new dictionary
|
||||
await fetchWordsForDictionary(newDictionaryId)
|
||||
|
||||
// Update navigation to use the new dictionary ID and remove isNewDictionary flag
|
||||
onNavigate?.('words', { dictionaryId: newDictionaryId, isNewDictionary: false })
|
||||
} else if (dictionaryId !== undefined && dictionaryId !== null) {
|
||||
// Update existing dictionary
|
||||
const response = await fetch(`${API_URL}/dictionaries/${dictionaryId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ name: dictionaryName.trim() }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка при обновлении словаря')
|
||||
}
|
||||
|
||||
setOriginalDictionaryName(dictionaryName.trim())
|
||||
if (dictionary) {
|
||||
setDictionary({ ...dictionary, name: dictionaryName.trim() })
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
setIsSavingName(false)
|
||||
}
|
||||
}
|
||||
|
||||
const showSaveButton = dictionaryName.trim() !== '' && dictionaryName.trim() !== originalDictionaryName
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="word-list">
|
||||
<div className="loading">Загрузка...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="word-list">
|
||||
<div className="error-message">{error}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="word-list">
|
||||
<button
|
||||
onClick={() => onNavigate?.('test-config')}
|
||||
className="close-x-button"
|
||||
title="Закрыть"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
{/* Dictionary name input */}
|
||||
<div className="dictionary-name-input-container">
|
||||
<input
|
||||
type="text"
|
||||
className="dictionary-name-input"
|
||||
value={dictionaryName}
|
||||
onChange={handleNameChange}
|
||||
placeholder="Введите название словаря"
|
||||
/>
|
||||
{showSaveButton && (
|
||||
<button
|
||||
className="dictionary-name-save-button"
|
||||
onClick={handleNameSave}
|
||||
disabled={isSavingName}
|
||||
title="Сохранить название"
|
||||
>
|
||||
✓
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Show add button and words list:
|
||||
- If dictionary exists (has dictionaryId), show regardless of name
|
||||
- If new dictionary (no dictionaryId), show only if name is set */}
|
||||
{((currentDictionaryId !== undefined && currentDictionaryId !== null && !isNewDict) || (isNewDict && dictionaryName.trim())) && (
|
||||
<>
|
||||
{(!words || words.length === 0) ? (
|
||||
<>
|
||||
<button onClick={() => onNavigate?.('add-words', { dictionaryId: currentDictionaryId, dictionaryName })} className="add-button">
|
||||
Добавить
|
||||
</button>
|
||||
<div className="empty-state">
|
||||
<p>Слов пока нет. Добавьте слова через экран "Добавить слова".</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button onClick={() => onNavigate?.('add-words', { dictionaryId: currentDictionaryId, dictionaryName })} className="add-button">
|
||||
Добавить
|
||||
</button>
|
||||
<div className="words-grid">
|
||||
{words.map((word) => (
|
||||
<div key={word.id} className="word-card">
|
||||
<div className="word-content">
|
||||
<div className="word-header">
|
||||
<h3 className="word-name">{word.name}</h3>
|
||||
</div>
|
||||
<div className="word-translation">{word.translation}</div>
|
||||
{word.description && (
|
||||
<div className="word-description">{word.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="word-stats">
|
||||
<span className="stat-success">{word.success || 0}</span>
|
||||
<span className="stat-separator"> | </span>
|
||||
<span className="stat-failure">{word.failure || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WordList
|
||||
|
||||
Reference in New Issue
Block a user