Exam ve Pdf Test Route URL

This commit is contained in:
Sedat ÖZTÜRK 2025-10-17 17:16:58 +03:00
parent cce976e158
commit 0dc6c7b521
3 changed files with 283 additions and 238 deletions

View file

@ -12644,7 +12644,7 @@
{
"key": "admin.coordinator.examDetail",
"path": "/admin/coordinator/exam/:id",
"componentPath": "@/views/coordinator/Exams",
"componentPath": "@/views/coordinator/ExamInterface/ExamInterface",
"routeType": "protected",
"authority": ["App.Coordinator.Exams"]
},
@ -12658,7 +12658,7 @@
{
"key": "admin.coordinator.assignmentDetail",
"path": "/admin/coordinator/assignment/:id",
"componentPath": "@/views/coordinator/Assignments",
"componentPath": "@/views/coordinator/ExamInterface/ExamInterface",
"routeType": "protected",
"authority": ["App.Coordinator.Assignments"]
},
@ -12672,7 +12672,7 @@
{
"key": "admin.coordinator.testDetail",
"path": "/admin/coordinator/test/:id",
"componentPath": "@/views/coordinator/Tests",
"componentPath": "@/views/coordinator/ExamInterface/PDFTestInterface",
"routeType": "protected",
"authority": ["App.Coordinator.Tests"]
},

View file

@ -1,40 +1,54 @@
import React, { useState, useEffect } from 'react';
import { QuestionRenderer } from './QuestionRenderer';
import { ExamNavigation } from './ExamNavigation';
import { ExamTimer } from './ExamTimer';
import { SecurityWarning } from './SecurityWarning';
import { Exam, ExamSession, StudentAnswer } from '@/types/coordinator';
import { useExamSecurity } from '@/utils/hooks/useExamSecurity';
import React, { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { QuestionRenderer } from './QuestionRenderer'
import { ExamNavigation } from './ExamNavigation'
import { ExamTimer } from './ExamTimer'
import { SecurityWarning } from './SecurityWarning'
import { ExamSession, StudentAnswer } from '@/types/coordinator'
import { useExamSecurity } from '@/utils/hooks/useExamSecurity'
import { useStoreState } from '@/store/store'
import { generateMockExam } from '@/mocks/mockExams'
interface StudentExamInterfaceProps {
exam: Exam;
studentId: string;
onExamComplete: (session: ExamSession) => void;
onExamSave?: (session: ExamSession) => void;
const ExamInterface: React.FC = () => {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const userId = useStoreState((state) => state.auth.user.id)
// TODO: Replace with actual API call to fetch exam by id
const exam = generateMockExam().find((e) => e.id === id)
if (!exam) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-6 text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Sınav Bulunamadı</h2>
<p className="text-gray-600 mb-6">İstenen sınav bulunamadı veya erişim yetkiniz yok.</p>
<button
onClick={() => navigate(-1)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors"
>
Geri Dön
</button>
</div>
</div>
)
}
export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
exam,
studentId,
onExamComplete,
onExamSave
}) => {
const [session, setSession] = useState<ExamSession>({
id: `session-${Date.now()}`,
examId: exam.id,
studentId,
studentId: userId,
startTime: new Date(),
answers: [],
status: 'in-progress',
timeRemaining: exam.timeLimit * 60
});
timeRemaining: exam.timeLimit * 60,
})
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
const [securityWarning, setSecurityWarning] = useState<{
show: boolean;
message: string;
type: 'warning' | 'error' | 'info';
}>({ show: false, message: '', type: 'warning' });
show: boolean
message: string
type: 'warning' | 'error' | 'info'
}>({ show: false, message: '', type: 'warning' })
// Security configuration
const securityConfig = {
@ -42,121 +56,150 @@ export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
disableCopyPaste: true,
disableDevTools: true,
fullScreenMode: true,
preventTabSwitch: true
};
preventTabSwitch: true,
}
useExamSecurity(securityConfig, session.status === 'in-progress');
useExamSecurity(securityConfig, session.status === 'in-progress')
// Save exam session to backend/localStorage
const saveExamSession = (sessionData: ExamSession) => {
// TODO: Replace with actual API call
console.log('Saving exam session:', sessionData)
localStorage.setItem(`exam-session-${sessionData.examId}`, JSON.stringify(sessionData))
}
// Complete exam and navigate
const handleExamComplete = (sessionData: ExamSession) => {
// TODO: Replace with actual API call
console.log('Exam completed:', sessionData)
saveExamSession(sessionData)
// Navigate to results or dashboard
// navigate('/admin/coordinator/exams')
}
// Auto-save mechanism
useEffect(() => {
const interval = setInterval(() => {
if (session.status === 'in-progress' && onExamSave) {
onExamSave(session);
if (session.status === 'in-progress') {
saveExamSession(session)
}
}, 30000); // Save every 30 seconds
}, 30000) // Save every 30 seconds
return () => clearInterval(interval);
}, [session, onExamSave]);
return () => clearInterval(interval)
}, [session])
const handleAnswerChange = (questionId: string, answer: string | string[]) => {
setSession(prev => {
const existingAnswerIndex = prev.answers.findIndex(a => a.questionId === questionId);
setSession((prev) => {
const existingAnswerIndex = prev.answers.findIndex((a) => a.questionId === questionId)
const newAnswer: StudentAnswer = {
questionId,
answer,
timeSpent: 0 // In a real app, track time spent per question
};
timeSpent: 0, // In a real app, track time spent per question
}
const newAnswers = existingAnswerIndex >= 0
? prev.answers.map((a, i) => i === existingAnswerIndex ? newAnswer : a)
: [...prev.answers, newAnswer];
const newAnswers =
existingAnswerIndex >= 0
? prev.answers.map((a, i) => (i === existingAnswerIndex ? newAnswer : a))
: [...prev.answers, newAnswer]
return {
...prev,
answers: newAnswers
};
});
};
answers: newAnswers,
}
})
}
const handleTimeUp = () => {
setSecurityWarning({
show: true,
message: 'Süre doldu! Sınav otomatik olarak teslim ediliyor...',
type: 'error'
});
type: 'error',
})
setTimeout(() => {
completeExam();
}, 3000);
};
completeExam()
}, 3000)
}
const completeExam = () => {
const completedSession: ExamSession = {
...session,
endTime: new Date(),
status: 'completed'
};
status: 'completed',
}
setSession(completedSession);
onExamComplete(completedSession);
};
setSession(completedSession)
handleExamComplete(completedSession)
}
const handleSubmitExam = () => {
const unanswered = exam.questions.filter(q =>
!session.answers.find(a => a.questionId === q.id && a.answer)
);
const unanswered = exam.questions.filter(
(q) => !session.answers.find((a) => a.questionId === q.id && a.answer),
)
if (unanswered.length > 0) {
const confirmSubmit = window.confirm(
`${unanswered.length} soru cevaplanmamış. Yine de sınavı teslim etmek istiyor musunuz?`
);
`${unanswered.length} soru cevaplanmamış. Yine de sınavı teslim etmek istiyor musunuz?`,
)
if (!confirmSubmit) return;
if (!confirmSubmit) return
}
completeExam();
};
completeExam()
}
const currentQuestion = exam.questions[currentQuestionIndex];
const currentAnswer = session.answers.find(a => a.questionId === currentQuestion?.id);
const currentQuestion = exam.questions[currentQuestionIndex]
const currentAnswer = session.answers.find((a) => a.questionId === currentQuestion?.id)
const navigateQuestion = (direction: 'next' | 'prev' | number) => {
if (typeof direction === 'number') {
setCurrentQuestionIndex(Math.max(0, Math.min(exam.questions.length - 1, direction)));
setCurrentQuestionIndex(Math.max(0, Math.min(exam.questions.length - 1, direction)))
} else if (direction === 'next') {
setCurrentQuestionIndex(prev => Math.min(exam.questions.length - 1, prev + 1));
setCurrentQuestionIndex((prev) => Math.min(exam.questions.length - 1, prev + 1))
} else {
setCurrentQuestionIndex(prev => Math.max(0, prev - 1));
setCurrentQuestionIndex((prev) => Math.max(0, prev - 1))
}
}
};
if (session.status === 'completed') {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-6 text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
<svg
className="w-8 h-8 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Sınav Tamamlandı!</h2>
<p className="text-gray-600 mb-6">
Sınavınız başarıyla teslim edildi. Sonuçlarınız değerlendirildikten sonra bilgilendirileceksiniz.
Sınavınız başarıyla teslim edildi. Sonuçlarınız değerlendirildikten sonra
bilgilendirileceksiniz.
</p>
<div className="text-sm text-gray-500">
Başlama: {session.startTime.toLocaleString('tr-TR')}<br />
Başlama: {session.startTime.toLocaleString('tr-TR')}
<br />
Bitiş: {session.endTime?.toLocaleString('tr-TR')}
</div>
</div>
</div>
);
)
}
return (
<div className="min-h-screen bg-gray-50">
<SecurityWarning
isVisible={securityWarning.show}
onDismiss={() => setSecurityWarning(prev => ({ ...prev, show: false }))}
onDismiss={() => setSecurityWarning((prev) => ({ ...prev, show: false }))}
message={securityWarning.message}
type={securityWarning.type}
/>
@ -172,11 +215,7 @@ export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
</div>
<div className="flex items-center space-x-4">
<ExamTimer
initialTime={exam.timeLimit * 60}
onTimeUp={handleTimeUp}
autoStart={true}
/>
<ExamTimer initialTime={exam.timeLimit * 60} onTimeUp={handleTimeUp} autoStart={true} />
<button
onClick={handleSubmitExam}
@ -210,7 +249,12 @@ export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
className="flex items-center space-x-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 disabled:bg-gray-50 disabled:text-gray-400 text-gray-700 rounded-lg font-medium transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
<span>Önceki</span>
</button>
@ -228,7 +272,12 @@ export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
>
<span>Sonraki</span>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
</div>
@ -246,5 +295,7 @@ export const StudentExamInterface: React.FC<StudentExamInterfaceProps> = ({
</div>
</div>
</div>
);
};
)
}
export default ExamInterface

View file

@ -1,41 +1,55 @@
import React, { useState, useEffect } from "react";
import { ExamTimer } from "./ExamTimer";
import { SecurityWarning } from "./SecurityWarning";
import { FaFileAlt, FaImage, FaCheckCircle } from "react-icons/fa";
import { Exam, ExamSession, StudentAnswer, AnswerKeyItem } from "@/types/coordinator";
import { useExamSecurity } from "@/utils/hooks/useExamSecurity";
import React, { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { ExamTimer } from './ExamTimer'
import { SecurityWarning } from './SecurityWarning'
import { FaFileAlt, FaImage, FaCheckCircle } from 'react-icons/fa'
import { Exam, ExamSession, StudentAnswer, AnswerKeyItem } from '@/types/coordinator'
import { useExamSecurity } from '@/utils/hooks/useExamSecurity'
import { useStoreState } from '@/store/store'
import { generateMockPDFTest } from '@/mocks/mockTests'
interface PDFTestInterfaceProps {
exam: Exam;
studentId: string;
onExamComplete: (session: ExamSession) => void;
onExamSave?: (session: ExamSession) => void;
const PDFTestInterface: React.FC = () => {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const userId = useStoreState((state) => state.auth.user.id)
// TODO: Replace with actual API call to fetch test by id
const exam = generateMockPDFTest()
if (!exam || exam.id !== id) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-6 text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Test Bulunamadı</h2>
<p className="text-gray-600 mb-6">İstenen test bulunamadı veya erişim yetkiniz yok.</p>
<button
onClick={() => navigate(-1)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors"
>
Geri Dön
</button>
</div>
</div>
)
}
export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
exam,
studentId,
onExamComplete,
onExamSave,
}) => {
const [session, setSession] = useState<ExamSession>({
id: `session-${Date.now()}`,
examId: exam.id,
studentId,
studentId: userId,
startTime: new Date(),
answers: [],
status: "in-progress",
status: 'in-progress',
timeRemaining: exam.timeLimit * 60,
});
})
const [answerKeyResponses, setAnswerKeyResponses] = useState<
Record<string, string | string[]>
>({});
const [answerKeyResponses, setAnswerKeyResponses] = useState<Record<string, string | string[]>>(
{},
)
const [securityWarning, setSecurityWarning] = useState<{
show: boolean;
message: string;
type: "warning" | "error" | "info";
}>({ show: false, message: "", type: "warning" });
show: boolean
message: string
type: 'warning' | 'error' | 'info'
}>({ show: false, message: '', type: 'warning' })
// Security configuration
const securityConfig = {
@ -44,74 +58,86 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
disableDevTools: true,
fullScreenMode: true,
preventTabSwitch: true,
};
}
useExamSecurity(securityConfig, session.status === "in-progress");
useExamSecurity(securityConfig, session.status === 'in-progress')
// Save test session to backend/localStorage
const saveTestSession = (sessionData: ExamSession) => {
// TODO: Replace with actual API call
console.log('Saving test session:', sessionData)
localStorage.setItem(`test-session-${sessionData.examId}`, JSON.stringify(sessionData))
}
// Complete test and navigate
const handleTestComplete = (sessionData: ExamSession) => {
// TODO: Replace with actual API call
console.log('Test completed:', sessionData)
saveTestSession(sessionData)
// Navigate to results or dashboard
// navigate('/admin/coordinator/tests')
}
// Auto-save mechanism
useEffect(() => {
const interval = setInterval(() => {
if (session.status === "in-progress" && onExamSave) {
onExamSave(session);
if (session.status === 'in-progress') {
saveTestSession(session)
}
}, 30000);
}, 30000)
return () => clearInterval(interval);
}, [session, onExamSave]);
return () => clearInterval(interval)
}, [session])
const handleAnswerChange = (itemId: string, answer: string | string[]) => {
setAnswerKeyResponses((prev) => ({
...prev,
[itemId]: answer,
}));
}))
// Update session answers
setSession((prev) => {
const existingAnswerIndex = prev.answers.findIndex(
(a) => a.questionId === itemId
);
const existingAnswerIndex = prev.answers.findIndex((a) => a.questionId === itemId)
const newAnswer: StudentAnswer = {
questionId: itemId,
answer,
timeSpent: 0,
};
}
const newAnswers =
existingAnswerIndex >= 0
? prev.answers.map((a, i) =>
i === existingAnswerIndex ? newAnswer : a
)
: [...prev.answers, newAnswer];
? prev.answers.map((a, i) => (i === existingAnswerIndex ? newAnswer : a))
: [...prev.answers, newAnswer]
return {
...prev,
answers: newAnswers,
};
});
};
}
})
}
const handleTimeUp = () => {
setSecurityWarning({
show: true,
message: "Süre doldu! Test otomatik olarak teslim ediliyor...",
type: "error",
});
message: 'Süre doldu! Test otomatik olarak teslim ediliyor...',
type: 'error',
})
setTimeout(() => {
completeExam();
}, 3000);
};
completeExam()
}, 3000)
}
const completeExam = () => {
const completedSession: ExamSession = {
...session,
endTime: new Date(),
status: "completed",
};
status: 'completed',
}
setSession(completedSession);
onExamComplete(completedSession);
};
setSession(completedSession)
handleTestComplete(completedSession)
}
const handleSubmitExam = () => {
const unanswered =
@ -120,45 +146,41 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
!answerKeyResponses[item.id] ||
(Array.isArray(answerKeyResponses[item.id]) &&
(answerKeyResponses[item.id] as string[]).length === 0) ||
(typeof answerKeyResponses[item.id] === "string" &&
!answerKeyResponses[item.id])
) || [];
(typeof answerKeyResponses[item.id] === 'string' && !answerKeyResponses[item.id]),
) || []
if (unanswered.length > 0) {
const confirmSubmit = window.confirm(
`${unanswered.length} soru cevaplanmamış. Yine de testi teslim etmek istiyor musunuz?`
);
`${unanswered.length} soru cevaplanmamış. Yine de testi teslim etmek istiyor musunuz?`,
)
if (!confirmSubmit) return;
if (!confirmSubmit) return
}
completeExam();
};
completeExam()
}
const getAnsweredCount = () => {
return (
exam.answerKeyTemplate?.filter((item) => {
const answer = answerKeyResponses[item.id];
const answer = answerKeyResponses[item.id]
if (Array.isArray(answer)) {
return answer.length > 0 && answer.some((a) => a.trim() !== "");
return answer.length > 0 && answer.some((a) => a.trim() !== '')
}
return answer && answer.toString().trim() !== "";
return answer && answer.toString().trim() !== ''
}).length || 0
);
};
)
}
const renderAnswerKeyItem = (item: AnswerKeyItem) => {
const currentAnswer = answerKeyResponses[item.id];
const currentAnswer = answerKeyResponses[item.id]
switch (item.type) {
case "multiple-choice":
case 'multiple-choice':
return (
<div className="space-y-2">
{item.options?.map((option, index) => (
<label
key={index}
className="flex items-center space-x-2 cursor-pointer"
>
<label key={index} className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name={`question-${item.id}`}
@ -171,20 +193,20 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
</label>
))}
</div>
);
)
case "fill-blank":
case 'fill-blank':
return (
<input
type="text"
value={(currentAnswer as string) || ""}
value={(currentAnswer as string) || ''}
onChange={(e) => handleAnswerChange(item.id, e.target.value)}
className="w-full text-sm border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Cevabınızı yazın..."
/>
);
)
case "true-false":
case 'true-false':
return (
<div className="flex space-x-4">
<label className="flex items-center space-x-2 cursor-pointer">
@ -192,7 +214,7 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
type="radio"
name={`question-${item.id}`}
value="true"
checked={currentAnswer === "true"}
checked={currentAnswer === 'true'}
onChange={(e) => handleAnswerChange(item.id, e.target.value)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
/>
@ -203,51 +225,47 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
type="radio"
name={`question-${item.id}`}
value="false"
checked={currentAnswer === "false"}
checked={currentAnswer === 'false'}
onChange={(e) => handleAnswerChange(item.id, e.target.value)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
/>
<span className="text-sm text-gray-700">Yanlış</span>
</label>
</div>
);
)
default:
return null;
return null
}
}
};
if (session.status === "completed") {
if (session.status === 'completed') {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-6 text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<FaCheckCircle className="w-8 h-8 text-green-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">
Test Tamamlandı!
</h2>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Test Tamamlandı!</h2>
<p className="text-gray-600 mb-6">
Testiniz başarıyla teslim edildi. Sonuçlarınız değerlendirildikten
sonra bilgilendirileceksiniz.
Testiniz başarıyla teslim edildi. Sonuçlarınız değerlendirildikten sonra
bilgilendirileceksiniz.
</p>
<div className="text-sm text-gray-500">
Başlama: {session.startTime.toLocaleString("tr-TR")}
Başlama: {session.startTime.toLocaleString('tr-TR')}
<br />
Bitiş: {session.endTime?.toLocaleString("tr-TR")}
Bitiş: {session.endTime?.toLocaleString('tr-TR')}
</div>
</div>
</div>
);
)
}
return (
<div className="min-h-screen bg-gray-50">
<SecurityWarning
isVisible={securityWarning.show}
onDismiss={() =>
setSecurityWarning((prev) => ({ ...prev, show: false }))
}
onDismiss={() => setSecurityWarning((prev) => ({ ...prev, show: false }))}
message={securityWarning.message}
type={securityWarning.type}
/>
@ -256,21 +274,14 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
<header className="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-40">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div>
<h1 className="text-xl font-semibold text-gray-900">
{exam.title}
</h1>
<h1 className="text-xl font-semibold text-gray-900">{exam.title}</h1>
<p className="text-sm text-gray-600">
PDF Test - {getAnsweredCount()} /{" "}
{exam.answerKeyTemplate?.length || 0} cevaplandı
PDF Test - {getAnsweredCount()} / {exam.answerKeyTemplate?.length || 0} cevaplandı
</p>
</div>
<div className="flex items-center space-x-4">
<ExamTimer
initialTime={exam.timeLimit * 60}
onTimeUp={handleTimeUp}
autoStart={true}
/>
<ExamTimer initialTime={exam.timeLimit * 60} onTimeUp={handleTimeUp} autoStart={true} />
<button
onClick={handleSubmitExam}
@ -288,29 +299,21 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
{/* Document Viewer */}
<div className="bg-white border border-gray-200 rounded-lg p-6">
<div className="flex items-center space-x-2 mb-4">
{exam.testDocument?.type === "pdf" ? (
{exam.testDocument?.type === 'pdf' ? (
<FaFileAlt className="w-5 h-5 text-red-600" />
) : (
<FaImage className="w-5 h-5 text-blue-600" />
)}
<h3 className="text-lg font-semibold text-gray-900">
Test Dokümanı
</h3>
<span className="text-sm text-gray-500">
({exam.testDocument?.name})
</span>
<h3 className="text-lg font-semibold text-gray-900">Test Dokümanı</h3>
<span className="text-sm text-gray-500">({exam.testDocument?.name})</span>
</div>
{exam.testDocument?.type === "pdf" ? (
{exam.testDocument?.type === 'pdf' ? (
<div
className="border border-gray-300 rounded-lg overflow-hidden"
style={{ height: "600px" }}
style={{ height: '600px' }}
>
<iframe
src={exam.testDocument.url}
className="w-full h-full"
title="Test PDF"
/>
<iframe src={exam.testDocument.url} className="w-full h-full" title="Test PDF" />
</div>
) : (
<div className="border border-gray-300 rounded-lg overflow-hidden">
@ -325,9 +328,7 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
{/* Answer Key */}
<div className="bg-white border border-gray-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Cevap Anahtarı
</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Cevap Anahtarı</h3>
<div className="space-y-6 max-h-96 overflow-y-auto">
{exam.answerKeyTemplate?.map((item) => {
@ -335,15 +336,13 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
answerKeyResponses[item.id] &&
(Array.isArray(answerKeyResponses[item.id])
? (answerKeyResponses[item.id] as string[]).length > 0
: answerKeyResponses[item.id].toString().trim() !== "");
: answerKeyResponses[item.id].toString().trim() !== '')
return (
<div
key={item.id}
className={`p-4 border-2 rounded-lg transition-all ${
isAnswered
? "border-green-200 bg-green-50"
: "border-gray-200 bg-white"
isAnswered ? 'border-green-200 bg-green-50' : 'border-gray-200 bg-white'
}`}
>
<div className="flex items-center justify-between mb-3">
@ -355,14 +354,12 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
{item.points} Puan
</span>
</div>
{isAnswered && (
<FaCheckCircle className="w-5 h-5 text-green-600" />
)}
{isAnswered && <FaCheckCircle className="w-5 h-5 text-green-600" />}
</div>
{renderAnswerKeyItem(item)}
</div>
);
)
})}
</div>
@ -370,8 +367,7 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
<div className="mt-6 pt-4 border-t border-gray-200">
<div className="flex items-center justify-between text-sm text-gray-600">
<span>
İlerleme: {getAnsweredCount()} /{" "}
{exam.answerKeyTemplate?.length || 0}
İlerleme: {getAnsweredCount()} / {exam.answerKeyTemplate?.length || 0}
</span>
<span>Toplam Puan: {exam.totalPoints}</span>
</div>
@ -379,11 +375,7 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{
width: `${
(getAnsweredCount() /
(exam.answerKeyTemplate?.length || 1)) *
100
}%`,
width: `${(getAnsweredCount() / (exam.answerKeyTemplate?.length || 1)) * 100}%`,
}}
/>
</div>
@ -392,5 +384,7 @@ export const PDFTestInterface: React.FC<PDFTestInterfaceProps> = ({
</div>
</div>
</div>
);
};
)
}
export default PDFTestInterface