dramaling-vocab-learning/frontend/components/review/shared/BaseTestComponent.tsx

136 lines
3.4 KiB
TypeScript

import React, { useState, useCallback, ReactNode } from 'react'
import { ErrorReportButton, TestHeader } from '@/components/review/shared'
import { BaseReviewProps } from '@/types/review'
/**
* 基礎測驗元件 - 提供所有測驗元件的共用功能
* 包含:標題、錯誤回報、測驗狀態管理、統一布局結構
*/
export interface BaseTestComponentProps extends BaseReviewProps {
testTitle: string
instructions?: string
children: ReactNode
showResult?: boolean
resultContent?: ReactNode
className?: string
}
interface TestState {
hasAnswered: boolean
userAnswer: string | null
showResult: boolean
}
export const BaseTestComponent: React.FC<BaseTestComponentProps> = ({
cardData,
testTitle,
instructions,
children,
showResult = false,
resultContent,
onReportError,
disabled = false,
className = ''
}) => {
const [testState, setTestState] = useState<TestState>({
hasAnswered: false,
userAnswer: null,
showResult: false
})
// 更新測驗狀態
const updateTestState = useCallback((updates: Partial<TestState>) => {
setTestState(prev => ({ ...prev, ...updates }))
}, [])
// 提供給子元件的狀態和方法
const testContext = {
testState,
updateTestState,
cardData,
disabled: disabled || testState.showResult
}
return (
<div className={`relative ${className}`}>
{/* 錯誤回報按鈕 */}
<div className="flex justify-end mb-2">
<ErrorReportButton onClick={onReportError} />
</div>
{/* 主要測驗容器 */}
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 測驗標題 */}
<TestHeader
title={testTitle}
difficultyLevel={cardData.difficultyLevel}
/>
{/* 說明文字 */}
{instructions && (
<p className="text-lg text-gray-700 mb-6 text-left">
{instructions}
</p>
)}
{/* 測驗內容區域 */}
<div className="test-content">
{React.cloneElement(children as React.ReactElement, { testContext })}
</div>
{/* 結果顯示區域 */}
{(showResult || testState.showResult) && resultContent && (
<div className="mt-6">
{resultContent}
</div>
)}
</div>
</div>
)
}
/**
* Hook for managing test answer state
* 提供測驗答題狀態管理的標準化邏輯
*/
export const useTestAnswer = (onAnswer: (answer: string) => void) => {
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
const [showResult, setShowResult] = useState(false)
const handleAnswer = useCallback((answer: string) => {
if (showResult) return
setSelectedAnswer(answer)
setShowResult(true)
onAnswer(answer)
}, [showResult, onAnswer])
const resetAnswer = useCallback(() => {
setSelectedAnswer(null)
setShowResult(false)
}, [])
return {
selectedAnswer,
showResult,
handleAnswer,
resetAnswer
}
}
/**
* Navigation integration types
* 為後續的導航系統整合做準備
*/
export interface TestNavigationState {
status: 'unanswered' | 'answered' | 'skipped'
canSkip: boolean
canContinue: boolean
}
export interface TestNavigationProps {
navigationState: TestNavigationState
onSkip?: () => void
onContinue?: () => void
}