diff --git a/app/Http/Controllers/Admin/QuizController.php b/app/Http/Controllers/Admin/QuizController.php new file mode 100644 index 0000000..a612700 --- /dev/null +++ b/app/Http/Controllers/Admin/QuizController.php @@ -0,0 +1,109 @@ +withCount('questions')->latest()->paginate(15); + return view('admin.quizzes.index', compact('quizzes')); + } + + public function create() + { + $categories = QuizCategory::where('is_active', true)->get(); + return view('admin.quizzes.create', compact('categories')); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'category_id' => 'required|exists:quiz_categories,id', + 'year' => 'nullable|integer', + 'type' => 'required|in:multiple_choice,descriptive,mixed', + 'duration_minutes' => 'required|integer|min:1', + 'has_negative_score' => 'boolean', + 'is_free' => 'boolean', + 'is_published' => 'boolean', + 'description' => 'nullable|string', + ]); + + $validated['slug'] = Str::slug($validated['title']) . '-' . time(); + $validated['has_negative_score'] = $request->has('has_negative_score'); + $validated['is_free'] = $request->has('is_free'); + $validated['is_published'] = $request->has('is_published'); + + $quiz = Quiz::create($validated); + + return redirect()->route('quizzes.questions.index', $quiz->id) + ->with('success', 'آزمون با موفقیت ایجاد شد. اکنون میتوانید سوالات را اضافه کنید.'); + } + + public function destroy(Quiz $quiz) + { + $quiz->delete(); + return redirect()->route('quizzes.index')->with('success', 'آزمون حذف شد.'); + } + + // --- Questions Management --- + + public function questions(Quiz $quiz) + { + $quiz->load(['questions.options', 'questions.subject']); + $subjects = QuizSubject::all(); + return view('admin.quizzes.questions', compact('quiz', 'subjects')); + } + + public function storeQuestion(Request $request, Quiz $quiz) + { + $request->validate([ + 'question_text' => 'required|string', + 'type' => 'required|in:multiple_choice,descriptive', + 'subject_id' => 'nullable|exists:quiz_subjects,id', + 'score' => 'required|numeric|min:0.5', + 'solution_explanation' => 'nullable|string', + // Options validation for multiple-choice questions + 'options' => 'required_if:type,multiple_choice|array|min:4|max:4', + 'options.*' => 'required_if:type,multiple_choice|string', + 'correct_option' => 'required_if:type,multiple_choice|integer|between:0,3', + ]); + + $question = $quiz->questions()->create([ + 'subject_id' => $request->subject_id, + 'question_text' => $request->question_text, + 'type' => $request->type, + 'score' => $request->score, + 'solution_explanation' => $request->solution_explanation, + 'order' => $quiz->questions()->count() + 1, + ]); + + // Save options if question is multiple choice + if ($request->type === 'multiple_choice' && $request->has('options')) { + foreach ($request->options as $index => $optionText) { + $question->options()->create([ + 'option_text' => $optionText, + 'is_correct' => ($index == $request->correct_option), + ]); + } + } + + return back()->with('success', 'سوال با موفقیت ثبت شد.'); + } + + public function destroyQuestion(Question $question) + { + $question->delete(); + return back()->with('success', 'سوال حذف شد.'); + } +} diff --git a/app/Models/Question.php b/app/Models/Question.php new file mode 100644 index 0000000..ab17241 --- /dev/null +++ b/app/Models/Question.php @@ -0,0 +1,46 @@ + 'float', + 'order' => 'integer', + ]; + + public function quiz() + { + return $this->belongsTo(Quiz::class, 'quiz_id'); + } + + public function subject() + { + return $this->belongsTo(QuizSubject::class, 'subject_id'); + } + + public function options() + { + return $this->hasMany(QuestionOption::class, 'question_id'); + } + + public function userAnswers() + { + return $this->hasMany(UserQuizAnswer::class, 'question_id'); + } +} diff --git a/app/Models/QuestionOption.php b/app/Models/QuestionOption.php new file mode 100644 index 0000000..5edc25c --- /dev/null +++ b/app/Models/QuestionOption.php @@ -0,0 +1,26 @@ + 'boolean', + ]; + + public function question() + { + return $this->belongsTo(Question::class, 'question_id'); + } +} diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php new file mode 100644 index 0000000..62b80b0 --- /dev/null +++ b/app/Models/Quiz.php @@ -0,0 +1,47 @@ + 'integer', + 'duration_minutes' => 'integer', + 'has_negative_score' => 'boolean', + 'is_free' => 'boolean', + 'is_published' => 'boolean', + ]; + + public function category() + { + return $this->belongsTo(QuizCategory::class, 'category_id'); + } + + public function questions() + { + return $this->hasMany(Question::class, 'quiz_id')->orderBy('order'); + } + + public function attempts() + { + return $this->hasMany(UserQuizAttempt::class, 'quiz_id'); + } +} diff --git a/app/Models/QuizCategory.php b/app/Models/QuizCategory.php new file mode 100644 index 0000000..fe4924a --- /dev/null +++ b/app/Models/QuizCategory.php @@ -0,0 +1,27 @@ + 'boolean', + ]; + + public function quizzes() + { + return $this->hasMany(Quiz::class, 'category_id'); + } +} diff --git a/app/Models/QuizSubject.php b/app/Models/QuizSubject.php new file mode 100644 index 0000000..7646b9e --- /dev/null +++ b/app/Models/QuizSubject.php @@ -0,0 +1,21 @@ +hasMany(Question::class, 'subject_id'); + } +} diff --git a/app/Models/UserQuizAnswer.php b/app/Models/UserQuizAnswer.php new file mode 100644 index 0000000..96eb941 --- /dev/null +++ b/app/Models/UserQuizAnswer.php @@ -0,0 +1,41 @@ + 'boolean', + 'earned_score' => 'float', + ]; + + public function attempt() + { + return $this->belongsTo(UserQuizAttempt::class, 'attempt_id'); + } + + public function question() + { + return $this->belongsTo(Question::class, 'question_id'); + } + + public function selectedOption() + { + return $this->belongsTo(QuestionOption::class, 'selected_option_id'); + } +} diff --git a/app/Models/UserQuizAttempt.php b/app/Models/UserQuizAttempt.php new file mode 100644 index 0000000..990117d --- /dev/null +++ b/app/Models/UserQuizAttempt.php @@ -0,0 +1,47 @@ + 'datetime', + 'completed_at' => 'datetime', + 'total_score' => 'float', + 'correct_answers' => 'integer', + 'wrong_answers' => 'integer', + 'unanswered' => 'integer', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function quiz() + { + return $this->belongsTo(Quiz::class, 'quiz_id'); + } + + public function answers() + { + return $this->hasMany(UserQuizAnswer::class, 'attempt_id'); + } +} diff --git a/resources/views/admin/quizzes/create.blade.php b/resources/views/admin/quizzes/create.blade.php new file mode 100644 index 0000000..60c2252 --- /dev/null +++ b/resources/views/admin/quizzes/create.blade.php @@ -0,0 +1,80 @@ +@extends('admin.layouts.app') + +@section('content') +
| عنوان آزمون | +دستهبندی | +سال | +نوع | +تعداد سوالات | +مدت (دقیقه) | +وضعیت | +عملیات | +
|---|---|---|---|---|---|---|---|
| {{ $quiz->title }} | +{{ optional($quiz->category)->title }} | +{{ $quiz->year ?? '-' }} | ++ @if($quiz->type === 'multiple_choice') + چهار گزینهای + @elseif($quiz->type === 'descriptive') + تشریحی + @else + ترکیبی + @endif + | +{{ $quiz->questions_count }} سوال | +{{ $quiz->duration_minutes }} دقیقه | ++ @if($quiz->is_published) + منتشر شده + @else + پیشنویس + @endif + | ++ مدیریت سوالات + + | +
| هیچ آزمونی ثبت نشده است. | +|||||||
{{ $question->question_text }}
+ + @if($question->type === 'multiple_choice') +هنوز سوالی برای این آزمون ثبت نشده است.
+ @endforelse +