feat: Add error reporting to all test modes and unify button position

- Added error report button to flip card mode
- Unified error report button position across all modes (top-right)
- Implemented error report modal with optional reason input
- Removed example sentences from quiz mode results
- Updated modal to display test mode and card information

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-16 01:51:54 +08:00
parent 96e205fd7f
commit 102643d96c
3 changed files with 663 additions and 36 deletions

View File

@ -8,6 +8,39 @@ export default function FlashcardsPage() {
const [selectedSet, setSelectedSet] = useState<number | null>(null)
const [searchTerm, setSearchTerm] = useState('')
const [filterTag, setFilterTag] = useState('all')
const [selectedCards, setSelectedCards] = useState<number[]>([])
const [showCheckModal, setShowCheckModal] = useState(false)
const [checkingCard, setCheckingCard] = useState<any>(null)
const [checkResults, setCheckResults] = useState<any>(null)
const [errorReports, setErrorReports] = useState<any[]>([
{
id: 1,
cardId: 1,
word: 'negotiate',
reportType: '發音錯誤',
description: '美式發音標記有誤',
reportedAt: '2小時前',
status: 'pending'
},
{
id: 2,
cardId: 3,
word: 'perspective',
reportType: '翻譯不準確',
description: '翻譯缺少其他常用含義',
reportedAt: '1天前',
status: 'pending'
},
{
id: 3,
cardId: 5,
word: 'implement',
reportType: '例句錯誤',
description: '例句時態使用不當',
reportedAt: '3天前',
status: 'pending'
}
])
// Mock data
const cardSets = [
@ -64,11 +97,66 @@ export default function FlashcardsPage() {
]
const flashcards = [
{ id: 1, word: 'negotiate', translation: '協商', setId: 1, mastery: 80, nextReview: '明天' },
{ id: 2, word: 'accomplish', translation: '完成', setId: 1, mastery: 60, nextReview: '今天' },
{ id: 3, word: 'perspective', translation: '觀點', setId: 2, mastery: 90, nextReview: '3天後' },
{ id: 4, word: 'substantial', translation: '大量的', setId: 2, mastery: 40, nextReview: '今天' },
{ id: 5, word: 'implement', translation: '實施', setId: 3, mastery: 70, nextReview: '明天' },
{
id: 1,
word: 'negotiate',
translation: '協商',
definition: 'To discuss something with someone in order to reach an agreement',
partOfSpeech: 'verb',
pronunciation: '/nɪˈɡoʊʃieɪt/',
example: 'We need to negotiate a better deal with our suppliers.',
setId: 1,
mastery: 80,
nextReview: '明天'
},
{
id: 2,
word: 'accomplish',
translation: '完成',
definition: 'To finish something successfully or to achieve something',
partOfSpeech: 'verb',
pronunciation: '/əˈkɒmplɪʃ/',
example: 'She accomplished her goal of running a marathon.',
setId: 1,
mastery: 60,
nextReview: '今天'
},
{
id: 3,
word: 'perspective',
translation: '觀點',
definition: 'A particular way of considering something',
partOfSpeech: 'noun',
pronunciation: '/pərˈspektɪv/',
example: 'From my perspective, this is the best solution.',
setId: 2,
mastery: 90,
nextReview: '3天後'
},
{
id: 4,
word: 'substantial',
translation: '大量的',
definition: 'Large in size, value, or importance',
partOfSpeech: 'adjective',
pronunciation: '/səbˈstænʃəl/',
example: 'The company made a substantial profit last year.',
setId: 2,
mastery: 40,
nextReview: '今天'
},
{
id: 5,
word: 'implement',
translation: '實施',
definition: 'To put a decision, plan, or agreement into effect',
partOfSpeech: 'verb',
pronunciation: '/ˈɪmplɪment/',
example: 'We need to implement new safety measures.',
setId: 3,
mastery: 70,
nextReview: '明天'
},
]
const tags = ['all', '影視', '商務', '日常', '考試', '科技', '口語', '正式', '基礎', '學術', '專業']
@ -184,6 +272,21 @@ export default function FlashcardsPage() {
>
</button>
<button
onClick={() => setActiveTab('error-reports')}
className={`py-4 px-1 border-b-2 font-medium text-sm relative ${
activeTab === 'error-reports'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{errorReports.filter(r => r.status === 'pending').length > 0 && (
<span className="absolute -top-1 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{errorReports.filter(r => r.status === 'pending').length}
</span>
)}
</button>
</nav>
</div>
@ -282,17 +385,51 @@ export default function FlashcardsPage() {
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"> {filteredCards.length} </h3>
<button className="text-primary hover:text-primary-hover font-medium text-sm">
</button>
<div className="flex gap-2">
{selectedCards.length > 0 && (
<button
onClick={() => {
setCheckResults(null)
setShowCheckModal(true)
}}
className="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition-colors text-sm font-medium flex items-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
({selectedCards.length})
</button>
)}
<button className="text-primary hover:text-primary-hover font-medium text-sm">
</button>
</div>
</div>
<div className="space-y-3">
{filteredCards.map(card => (
<div key={card.id} className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<input type="checkbox" className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded" />
<div>
<input
type="checkbox"
checked={selectedCards.includes(card.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedCards([...selectedCards, card.id])
} else {
setSelectedCards(selectedCards.filter(id => id !== card.id))
}
}}
className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
/>
<div
onClick={() => {
setCheckingCard(card)
setCheckResults(null)
setShowCheckModal(true)
}}
className="cursor-pointer hover:text-primary transition-colors"
>
<div className="font-semibold">{card.word}</div>
<div className="text-sm text-gray-600">{card.translation}</div>
</div>
@ -336,6 +473,121 @@ export default function FlashcardsPage() {
</Link>
</div>
)}
{activeTab === 'error-reports' && (
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">
({errorReports.filter(r => r.status === 'pending').length} )
</h3>
{errorReports.filter(r => r.status === 'pending').length > 0 && (
<button
onClick={() => {
// 一鍵檢測所有錯誤回報
const pendingReports = errorReports.filter(r => r.status === 'pending')
setCheckingCard({
isErrorList: true,
reports: pendingReports
})
setCheckResults(null)
setShowCheckModal(true)
}}
className="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition-colors text-sm font-medium flex items-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
)}
</div>
{errorReports.length === 0 ? (
<div className="text-center py-12">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-600"></p>
</div>
) : (
<div className="space-y-3">
{errorReports.map(report => (
<div
key={report.id}
className={`bg-white border rounded-lg p-4 hover:shadow-md transition-shadow ${
report.status === 'resolved' ? 'opacity-60' : ''
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h4 className="font-semibold text-lg">{report.word}</h4>
<span className={`px-2 py-1 rounded-full text-xs ${
report.status === 'pending'
? 'bg-yellow-100 text-yellow-700'
: 'bg-green-100 text-green-700'
}`}>
{report.status === 'pending' ? '待處理' : '已解決'}
</span>
<span className="px-2 py-1 bg-red-100 text-red-700 rounded-full text-xs">
{report.reportType}
</span>
</div>
<p className="text-gray-600 mb-2">{report.description}</p>
<div className="text-sm text-gray-500">
{report.reportedAt}
</div>
</div>
<div className="flex gap-2">
{report.status === 'pending' && (
<>
<button
onClick={() => {
// 檢測單個錯誤
const card = flashcards.find(c => c.id === report.cardId)
if (card) {
setCheckingCard({
...card,
errorReport: report
})
setCheckResults(null)
setShowCheckModal(true)
}
}}
className="text-green-600 hover:text-green-700 p-2"
title="檢測此錯誤"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<button
onClick={() => {
// 標記為已解決
setErrorReports(errorReports.map(r =>
r.id === report.id ? { ...r, status: 'resolved' } : r
))
}}
className="text-gray-400 hover:text-gray-600 p-2"
title="忽略此錯誤"
>
<svg className="w-5 h-5" 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>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
@ -398,6 +650,203 @@ export default function FlashcardsPage() {
</div>
</div>
</div>
{/* Smart Check Modal */}
{showCheckModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
<div className="bg-white rounded-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto">
<div className="p-6 border-b">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">🤖 </h2>
<button
onClick={() => {
setShowCheckModal(false)
setCheckingCard(null)
setCheckResults(null)
}}
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>
</div>
<div className="p-6">
{!checkResults ? (
// 檢測前
<div>
<div className="mb-4">
<p className="text-gray-600 mb-4">
{checkingCard?.isErrorList
? `即將檢測 ${checkingCard.reports.length} 個錯誤回報`
: checkingCard?.errorReport
? `正在檢測錯誤回報:${checkingCard.errorReport.reportType}`
: checkingCard
? `正在檢測詞卡「${checkingCard.word}」的內容...`
: `即將檢測 ${selectedCards.length} 個詞卡的內容正確性`}
</p>
</div>
{checkingCard && !checkingCard.isErrorList && (
<div className="bg-gray-50 rounded-lg p-4 mb-4">
{checkingCard.errorReport && (
<div className="mb-3 p-3 bg-yellow-50 border border-yellow-200 rounded">
<div className="text-sm font-medium text-yellow-800 mb-1">
{checkingCard.errorReport.reportType}
</div>
<div className="text-sm text-yellow-700">
{checkingCard.errorReport.description}
</div>
</div>
)}
<div className="space-y-2">
<div><strong></strong>{checkingCard.word}</div>
<div><strong></strong>{checkingCard.translation}</div>
<div><strong></strong>{checkingCard.definition}</div>
<div><strong></strong>{checkingCard.partOfSpeech}</div>
<div><strong></strong>{checkingCard.pronunciation}</div>
<div><strong></strong>{checkingCard.example}</div>
</div>
</div>
)}
{checkingCard?.isErrorList && (
<div className="bg-gray-50 rounded-lg p-4 mb-4 max-h-60 overflow-y-auto">
<div className="text-sm font-medium text-gray-700 mb-2"></div>
<div className="space-y-2">
{checkingCard.reports.map((report, idx) => (
<div key={idx} className="bg-white p-2 rounded border border-gray-200">
<div className="font-medium">{report.word}</div>
<div className="text-xs text-gray-600">
{report.reportType}: {report.description}
</div>
</div>
))}
</div>
</div>
)}
<button
onClick={() => {
// 模擬檢測過程
setTimeout(() => {
const isErrorList = checkingCard?.isErrorList
const totalCount = isErrorList
? checkingCard.reports.length
: checkingCard
? 1
: selectedCards.length
setCheckResults({
totalChecked: totalCount,
isErrorList: isErrorList,
errors: [
{
field: '發音',
original: '/nɪˈɡoʊʃieɪt/',
corrected: '/nɪˈɡəʊʃieɪt/',
reason: '英式發音標記錯誤'
},
{
field: '例句',
original: 'We need to negotiate a better deal with our suppliers.',
corrected: 'We need to negotiate a better deal with our suppliers.',
reason: '例句文法正確,但可加入更多上下文'
}
],
suggestions: [
'建議添加同義詞bargain, discuss',
'建議添加反義詞refuse, reject',
'可補充詞根詞綴說明'
]
})
}, 2000)
}}
className="w-full bg-green-500 text-white py-3 rounded-lg hover:bg-green-600 transition-colors font-medium"
>
</button>
</div>
) : (
// 檢測結果
<div>
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 className="text-lg font-semibold"></h3>
</div>
<p className="text-gray-600">
{checkResults.isErrorList
? `已檢測 ${checkResults.totalChecked} 個錯誤回報,發現 ${checkResults.errors.length} 個問題`
: `已檢測 ${checkResults.totalChecked} 個詞卡,發現 ${checkResults.errors.length} 個問題`}
</p>
</div>
{checkResults.errors.length > 0 && (
<div className="mb-4">
<h4 className="font-semibold mb-2">📝 </h4>
<div className="space-y-3">
{checkResults.errors.map((error, idx) => (
<div key={idx} className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<div className="font-medium text-yellow-800 mb-1">{error.field}</div>
<div className="text-sm space-y-1">
<div><span className="text-gray-600"></span> {error.original}</div>
<div><span className="text-gray-600"></span> <span className="text-green-600">{error.corrected}</span></div>
<div className="text-xs text-gray-500">{error.reason}</div>
</div>
</div>
))}
</div>
</div>
)}
{checkResults.suggestions.length > 0 && (
<div className="mb-4">
<h4 className="font-semibold mb-2">💡 </h4>
<ul className="space-y-1">
{checkResults.suggestions.map((suggestion, idx) => (
<li key={idx} className="flex items-start gap-2 text-sm text-gray-600">
<span className="text-primary"></span>
{suggestion}
</li>
))}
</ul>
</div>
)}
<div className="flex gap-3">
<button
onClick={() => {
alert('已自動修正所有問題!')
setShowCheckModal(false)
setCheckResults(null)
setCheckingCard(null)
}}
className="flex-1 bg-primary text-white py-2 rounded-lg hover:bg-primary-hover transition-colors font-medium"
>
</button>
<button
onClick={() => {
setShowCheckModal(false)
setCheckResults(null)
setCheckingCard(null)
}}
className="flex-1 border border-gray-300 py-2 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
</div>
</div>
)}
</div>
</div>
</div>
)}
</div>
)
}

View File

@ -15,6 +15,9 @@ export default function LearnPage() {
const [isRecording, setIsRecording] = useState(false)
const [audioPlaying, setAudioPlaying] = useState(false)
const [modalImage, setModalImage] = useState<string | null>(null)
const [showReportModal, setShowReportModal] = useState(false)
const [reportReason, setReportReason] = useState('')
const [reportingCard, setReportingCard] = useState<any>(null)
// Mock data with real example images
const cards = [
@ -214,6 +217,23 @@ export default function LearnPage() {
{mode === 'flip' ? (
/* Flip Card Mode */
<div className="relative">
{/* Error Report Button for Flip Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="text-red-500 hover:text-red-600 text-sm flex items-center gap-1"
title="回報錯誤"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
<div
className="relative w-full h-96 cursor-pointer"
onClick={handleFlip}
@ -315,17 +335,35 @@ export default function LearnPage() {
</div>
) : mode === 'quiz' ? (
/* Quiz Mode - 選擇題:英文定義選中文翻譯 */
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-2"></div>
<div className="text-xl text-gray-800 leading-relaxed">
{currentCard.definition}
</div>
<div className="text-sm text-gray-500 mt-2">
({currentCard.partOfSpeech})
</div>
<div className="relative">
{/* Error Report Button for Quiz Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="text-red-500 hover:text-red-600 text-sm flex items-center gap-1"
title="回報錯誤"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-2"></div>
<div className="text-xl text-gray-800 leading-relaxed">
{currentCard.definition}
</div>
<div className="text-sm text-gray-500 mt-2">
({currentCard.partOfSpeech})
</div>
</div>
<div className="space-y-3">
{quizOptions.map((option, idx) => (
<button
@ -359,19 +397,31 @@ export default function LearnPage() {
))}
</div>
{showResult && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<div className="text-sm font-semibold text-gray-700 mb-2"></div>
<div className="text-gray-600">{currentCard.example}</div>
<div className="text-gray-500 text-sm mt-1">{currentCard.exampleTranslation}</div>
</div>
)}
</div>
</div>
) : mode === 'fill' ? (
/* Fill in the Blank Mode - 填空題 */
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-4"></div>
<div className="relative">
{/* Error Report Button for Fill Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="text-red-500 hover:text-red-600 text-sm flex items-center gap-1"
title="回報錯誤"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-4"></div>
{/* Example Image */}
{currentCard.exampleImage && (
@ -477,12 +527,31 @@ export default function LearnPage() {
</div>
</div>
)}
</div>
</div>
) : mode === 'listening' ? (
/* Listening Test Mode - 聽力測試 */
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6 text-center">
<div className="text-sm text-gray-600 mb-4"></div>
<div className="relative">
{/* Error Report Button for Listening Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="text-red-500 hover:text-red-600 text-sm flex items-center gap-1"
title="回報錯誤"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6 text-center">
<div className="text-sm text-gray-600 mb-4"></div>
{/* Audio Play Button */}
<button
@ -543,12 +612,31 @@ export default function LearnPage() {
</div>
</div>
)}
</div>
</div>
) : mode === 'speaking' ? (
/* Speaking Test Mode - 口說測試 */
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-4"></div>
<div className="relative">
{/* Error Report Button for Speaking Mode */}
<div className="flex justify-end mb-2">
<button
onClick={() => {
setReportingCard(currentCard)
setShowReportModal(true)
}}
className="text-red-500 hover:text-red-600 text-sm flex items-center gap-1"
title="回報錯誤"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="mb-6">
<div className="text-sm text-gray-600 mb-4"></div>
{/* Target Sentence */}
<div className="p-6 bg-gray-50 rounded-lg mb-6">
@ -622,6 +710,7 @@ export default function LearnPage() {
)}
</div>
</div>
</div>
) : null}
{/* Navigation Buttons */}
@ -682,6 +771,84 @@ export default function LearnPage() {
</div>
</div>
)}
{/* Error Report Modal */}
{showReportModal && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"
onClick={() => setShowReportModal(false)}
>
<div
className="bg-white rounded-lg shadow-xl max-w-md w-full p-6"
onClick={(e) => e.stopPropagation()}
>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"></h3>
<button
onClick={() => setShowReportModal(false)}
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>
<div className="mb-4">
<div className="text-sm text-gray-600 mb-2">
<span className="font-medium">{reportingCard?.word}</span>
</div>
<div className="text-sm text-gray-600 mb-2">
{mode === 'flip' ? '翻卡模式' : mode === 'quiz' ? '選擇題' : mode === 'fill' ? '填空題' : mode === 'listening' ? '聽力測試' : '口說測試'}
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<textarea
value={reportReason}
onChange={(e) => setReportReason(e.target.value)}
placeholder="請描述錯誤內容..."
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-primary focus:border-primary"
rows={3}
/>
</div>
<div className="flex gap-3">
<button
onClick={() => {
// Submit error report
console.log('Error reported:', {
card: reportingCard,
mode,
reason: reportReason
})
setShowReportModal(false)
setReportReason('')
setReportingCard(null)
// Show success message (could add a toast notification here)
alert('感謝您的回報,我們會盡快處理!')
}}
className="flex-1 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors"
>
</button>
<button
onClick={() => {
setShowReportModal(false)
setReportReason('')
setReportingCard(null)
}}
className="flex-1 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
>
</button>
</div>
</div>
</div>
)}
</div>
)
}

View File

@ -147,6 +147,13 @@
- 批量重設學習進度
- 批量導出
- **智能檢測**
- 單一詞彙檢測
- 全面檢查指定詞彙所有內容是否有問題,並修正錯誤內容
- 點擊指定內容進行檢查,並修正錯誤內容
- 錯誤清單一鍵檢測
- 點擊後及針對當前錯誤回報系統中清單進行檢測,一一修正錯誤內容
#### 1.3.3 組織功能
- **標籤系統**
- 預設標籤(動詞、名詞、片語、俚語等)
@ -204,6 +211,10 @@
- 聽力測試(聽音選詞)
- 口說測試 (念例句)
- **錯誤回報**
- 翻卡模式及所有測驗模式,都要設定錯誤回報
- 點擊錯誤回報後,可以輸入錯誤原因,可以不填寫直接送出
- **沉浸模式**
- 全螢幕學習
- 自動播放(可調速度)