Add Quiz feature: 10-question quizzes with progressive scoring (max 40 pts)
- Quizzes table with questions, answer options, attempts, answers - Question types: multiple_choice, exclusion, true_false, free_text - Progressive scoring: [1,1,2,2,3,3,4,6,8,10] = max 40 per quiz - Alpine.js countdown timer per question with auto-submit on timeout - Admin: CRUD for quizzes + per-question editor, JSON export/import - Child: quiz overview with best scores, question view, result breakdown - Nav: Quiz link in child header and admin sidebar
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\{Quiz, QuizQuestion, QuizAnswerOption, Subject};
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class QuizController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$quizzes = Quiz::with('subject')->withCount('questions')->latest()->get();
|
||||
return view('admin.quizzes.index', compact('quizzes'));
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$subjects = Subject::all();
|
||||
return view('admin.quizzes.create', compact('subjects'));
|
||||
}
|
||||
|
||||
public function store(Request $r) {
|
||||
$r->validate(['title'=>'required|string|max:120','subject_id'=>'required|exists:subjects,id','description'=>'nullable|string|max:500']);
|
||||
$quiz = Quiz::create($r->only('title','subject_id','description') + ['active'=>true]);
|
||||
return redirect()->route('admin.quizzes.edit', $quiz)->with('success','Quiz erstellt – jetzt Fragen hinzufügen.');
|
||||
}
|
||||
|
||||
public function edit(Quiz $quiz) {
|
||||
$subjects = Subject::all();
|
||||
$questions = $quiz->questions()->with('answerOptions')->get();
|
||||
return view('admin.quizzes.edit', compact('quiz','subjects','questions'));
|
||||
}
|
||||
|
||||
public function update(Request $r, Quiz $quiz) {
|
||||
$r->validate(['title'=>'required|string|max:120','subject_id'=>'required|exists:subjects,id','description'=>'nullable|string|max:500']);
|
||||
$quiz->update($r->only('title','subject_id','description') + ['active'=>$r->boolean('active')]);
|
||||
return back()->with('success','Quiz gespeichert.');
|
||||
}
|
||||
|
||||
public function destroy(Quiz $quiz) {
|
||||
$quiz->delete();
|
||||
return redirect()->route('admin.quizzes.index')->with('success','Quiz gelöscht.');
|
||||
}
|
||||
|
||||
public function export(Quiz $quiz) {
|
||||
$quiz->load('subject','questions.answerOptions');
|
||||
$data = [
|
||||
'subject' => $quiz->subject->slug,
|
||||
'title' => $quiz->title,
|
||||
'description' => $quiz->description,
|
||||
'questions' => $quiz->questions->map(fn($q) => [
|
||||
'type' => $q->type,
|
||||
'question_text' => $q->question_text,
|
||||
'time_limit' => $q->time_limit,
|
||||
'correct_answer' => $q->correct_answer,
|
||||
'options' => $q->answerOptions->map(fn($o) => [
|
||||
'text' => $o->text,
|
||||
'is_correct' => (bool)$o->is_correct,
|
||||
])->values(),
|
||||
])->values(),
|
||||
];
|
||||
$filename = 'quiz-'.now()->format('Y-m-d').'-'.str($quiz->title)->slug().'.json';
|
||||
return response()->streamDownload(
|
||||
fn() => print(json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)),
|
||||
$filename, ['Content-Type'=>'application/json']
|
||||
);
|
||||
}
|
||||
|
||||
public function import(Request $r) {
|
||||
$r->validate(['file'=>'required|file|mimes:json|max:4096']);
|
||||
$raw = json_decode(file_get_contents($r->file('file')->getRealPath()), true);
|
||||
if (!is_array($raw) || !isset($raw['title'],$raw['subject'],$raw['questions'])) {
|
||||
return back()->with('error','Ungültiges JSON-Format. Felder: title, subject, questions erwartet.');
|
||||
}
|
||||
$subject = Subject::where('slug',$raw['subject'])->first();
|
||||
if (!$subject) return back()->with('error','Unbekanntes Fach: '.$raw['subject']);
|
||||
|
||||
DB::transaction(function() use ($raw,$subject) {
|
||||
$quiz = Quiz::create([
|
||||
'subject_id' => $subject->id,
|
||||
'title' => $raw['title'],
|
||||
'description' => $raw['description'] ?? null,
|
||||
'active' => true,
|
||||
]);
|
||||
foreach (array_slice($raw['questions'],0,10) as $i => $item) {
|
||||
$q = QuizQuestion::create([
|
||||
'quiz_id' => $quiz->id,
|
||||
'sort_order' => $i,
|
||||
'type' => $item['type'] ?? 'multiple_choice',
|
||||
'question_text' => $item['question_text'],
|
||||
'time_limit' => $item['time_limit'] ?? null,
|
||||
'correct_answer' => $item['correct_answer'] ?? null,
|
||||
]);
|
||||
foreach (($item['options'] ?? []) as $j => $opt) {
|
||||
QuizAnswerOption::create([
|
||||
'quiz_question_id' => $q->id,
|
||||
'text' => $opt['text'],
|
||||
'is_correct' => $opt['is_correct'] ?? false,
|
||||
'sort_order' => $j,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return redirect()->route('admin.quizzes.index')->with('success','Quiz "'.$raw['title'].'" importiert.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user