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

254 lines
9.6 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, useEffect } from 'react'
import { flashcardsService, type CreateFlashcardRequest, type CardSet } from '@/lib/services/flashcards'
interface FlashcardFormProps {
cardSets: CardSet[]
initialData?: Partial<CreateFlashcardRequest & { id: string }>
isEdit?: boolean
onSuccess: () => void
onCancel: () => void
}
export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess, onCancel }: FlashcardFormProps) {
// 找到預設卡組或第一個卡組
const getDefaultCardSetId = () => {
if (initialData?.cardSetId) return initialData.cardSetId
// 優先選擇預設卡組
const defaultCardSet = cardSets.find(set => set.isDefault)
if (defaultCardSet) return defaultCardSet.id
// 如果沒有預設卡組,選擇第一個卡組
if (cardSets.length > 0) return cardSets[0].id
// 如果沒有任何卡組,返回空字串
return ''
}
const [formData, setFormData] = useState<CreateFlashcardRequest>({
cardSetId: getDefaultCardSetId(),
english: initialData?.english || '',
chinese: initialData?.chinese || '',
pronunciation: initialData?.pronunciation || '',
partOfSpeech: initialData?.partOfSpeech || '名詞',
example: initialData?.example || '',
})
// 當 cardSets 改變時,重新設定 cardSetId處理初始載入的情況
React.useEffect(() => {
if (!formData.cardSetId && cardSets.length > 0) {
const defaultId = getDefaultCardSetId()
if (defaultId) {
setFormData(prev => ({ ...prev, cardSetId: defaultId }))
}
}
}, [cardSets])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const partOfSpeechOptions = [
'名詞', '動詞', '形容詞', '副詞', '介詞', '連詞', '感嘆詞', '代詞', '冠詞'
]
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 || '操作失敗')
}
} catch (err) {
setError('操作失敗,請重試')
} finally {
setLoading(false)
}
}
const handleChange = (field: keyof CreateFlashcardRequest, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }))
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">
{isEdit ? '編輯詞卡' : '新增詞卡'}
</h2>
<button
onClick={onCancel}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<p className="text-red-600 text-sm">{error}</p>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
{/* 詞卡集合選擇 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
{cardSets.length === 0 ? (
<div className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-100 text-gray-500">
...
</div>
) : (
<select
value={formData.cardSetId}
onChange={(e) => handleChange('cardSetId', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
required
>
{/* 如果沒有選中任何卡組,顯示提示 */}
{!formData.cardSetId && (
<option value="" disabled>
</option>
)}
{/* 先顯示預設卡組 */}
{cardSets
.filter(set => set.isDefault)
.map(set => (
<option key={set.id} value={set.id}>
📂 {set.name} ()
</option>
))}
{/* 再顯示其他卡組 */}
{cardSets
.filter(set => !set.isDefault)
.map(set => (
<option key={set.id} value={set.id}>
{set.name}
</option>
))}
</select>
)}
</div>
{/* 英文單字 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
value={formData.english}
onChange={(e) => handleChange('english', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如negotiate"
required
/>
</div>
{/* 中文翻譯 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
value={formData.chinese}
onChange={(e) => handleChange('chinese', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如:談判,協商"
required
/>
</div>
{/* 詞性和發音 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<select
value={formData.partOfSpeech}
onChange={(e) => handleChange('partOfSpeech', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
required
>
{partOfSpeechOptions.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
value={formData.pronunciation}
onChange={(e) => handleChange('pronunciation', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如:/nɪˈɡoʊʃieɪt/"
/>
</div>
</div>
{/* 例句 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<textarea
value={formData.example}
onChange={(e) => handleChange('example', e.target.value)}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如We need to negotiate the contract terms."
/>
</div>
{/* 操作按鈕 */}
<div className="flex justify-end space-x-4 pt-4">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors"
>
</button>
<button
type="submit"
disabled={loading || cardSets.length === 0 || !formData.cardSetId}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? '處理中...' :
cardSets.length === 0 ? '載入中...' :
(isEdit ? '更新詞卡' : '新增詞卡')}
</button>
</div>
</form>
</div>
</div>
</div>
)
}