dramaling-vocab-learning/frontend/components/FlashcardForm.tsx

228 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React, { useState } from 'react'
import { flashcardsService, type CreateFlashcardRequest, type Flashcard } from '@/lib/services/flashcards'
import AudioPlayer from './AudioPlayer'
interface FlashcardFormProps {
cardSets?: any[] // 保持相容性
initialData?: Partial<Flashcard>
isEdit?: boolean
onSuccess: () => void
onCancel: () => void
}
export function FlashcardForm({ initialData, isEdit = false, onSuccess, onCancel }: FlashcardFormProps) {
const [formData, setFormData] = useState<CreateFlashcardRequest>({
word: initialData?.word || '',
translation: initialData?.translation || '',
definition: initialData?.definition || '',
pronunciation: initialData?.pronunciation || '',
partOfSpeech: initialData?.partOfSpeech || 'noun',
example: initialData?.example || '',
exampleTranslation: initialData?.exampleTranslation || '',
difficultyLevel: initialData?.difficultyLevel || 'A2',
})
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError(null)
try {
let result
if (isEdit && initialData?.id) {
result = await flashcardsService.updateFlashcard(initialData.id, formData)
} else {
result = await flashcardsService.createFlashcard(formData)
}
if (result.success) {
onSuccess()
} else {
setError(result.error || `Failed to ${isEdit ? 'update' : 'create'} flashcard`)
}
} catch (error) {
setError(error instanceof Error ? error.message : 'An unexpected error occurred')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
{error}
</div>
)}
<div>
<label htmlFor="word" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
id="word"
name="word"
value={formData.word}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="請輸入單字"
/>
</div>
<div>
<label htmlFor="translation" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
id="translation"
name="translation"
value={formData.translation}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="請輸入中文翻譯"
/>
</div>
<div>
<label htmlFor="definition" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<textarea
id="definition"
name="definition"
value={formData.definition}
onChange={handleChange}
required
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="請輸入英文定義"
/>
</div>
<div>
<label htmlFor="pronunciation" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<div className="flex gap-2">
<input
type="text"
id="pronunciation"
name="pronunciation"
value={formData.pronunciation}
onChange={handleChange}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="例如: /wɜːrd/"
/>
{formData.pronunciation && (
<AudioPlayer text={formData.word} />
)}
</div>
</div>
<div>
<label htmlFor="partOfSpeech" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<select
id="partOfSpeech"
name="partOfSpeech"
value={formData.partOfSpeech}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="noun"> (noun)</option>
<option value="verb"> (verb)</option>
<option value="adjective"> (adjective)</option>
<option value="adverb"> (adverb)</option>
<option value="preposition"> (preposition)</option>
<option value="interjection"> (interjection)</option>
<option value="phrase"> (phrase)</option>
</select>
</div>
<div>
<label htmlFor="difficultyLevel" className="block text-sm font-medium text-gray-700 mb-2">
CEFR
</label>
<select
id="difficultyLevel"
name="difficultyLevel"
value={formData.difficultyLevel}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="A1">A1 - </option>
<option value="A2">A2 - </option>
<option value="B1">B1 - </option>
<option value="B2">B2 - </option>
<option value="C1">C1 - </option>
<option value="C2">C2 - </option>
</select>
</div>
<div>
<label htmlFor="example" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<textarea
id="example"
name="example"
value={formData.example}
onChange={handleChange}
required
rows={2}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="請輸入例句"
/>
</div>
<div>
<label htmlFor="exampleTranslation" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<textarea
id="exampleTranslation"
name="exampleTranslation"
value={formData.exampleTranslation}
onChange={handleChange}
rows={2}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="請輸入例句的中文翻譯"
/>
</div>
<div className="flex gap-3 pt-4">
<button
type="submit"
disabled={loading}
className="flex-1 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200"
>
{loading ? (isEdit ? '更新中...' : '創建中...') : (isEdit ? '更新詞卡' : '創建詞卡')}
</button>
<button
type="button"
onClick={onCancel}
disabled={loading}
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 disabled:bg-gray-100 transition-colors duration-200"
>
</button>
</div>
</form>
)
}