feat: Lernapp mit Mathe/Deutsch/Englisch, Münzsystem und Belohnungen

This commit is contained in:
root
2026-05-05 14:41:09 +00:00
parent 21e40cd2da
commit bd1640994c
45 changed files with 1522 additions and 58 deletions
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\QuestionAttempt;
use App\Models\RewardRedemption;
class DashboardController extends Controller {
public function index() {
$children = User::where('role','child')->count();
$questions = \App\Models\Question::count();
$attempts_today = QuestionAttempt::whereDate('created_at', today())->count();
$pending = RewardRedemption::where('status','pending')->count();
$topKids = User::where('role','child')->orderByDesc('points')->take(5)->get();
$recentAttempts = QuestionAttempt::with(['user','question.subject'])
->latest('id')->take(10)->get();
return view('admin.dashboard', compact(
'children','questions','attempts_today','pending','topKids','recentAttempts'
));
}
}
@@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Question;
use App\Models\Subject;
use App\Models\AnswerOption;
use Illuminate\Http\Request;
class QuestionController extends Controller {
public function index(Request $r) {
$subjects = Subject::all();
$query = Question::with('subject')->latest();
if ($r->filled('subject')) $query->where('subject_id', $r->subject);
$questions = $query->paginate(20)->withQueryString();
return view('admin.questions.index', compact('questions','subjects'));
}
public function create() {
$subjects = Subject::all();
return view('admin.questions.create', compact('subjects'));
}
public function store(Request $r) {
$r->validate([
'subject_id' => 'required|exists:subjects,id',
'question_text' => 'required|string',
'type' => 'required|in:multiple_choice,number_input',
'difficulty' => 'required|in:1,2,3',
'options' => 'required_if:type,multiple_choice|array|min:2',
'options.*' => 'required|string',
'correct' => 'required_if:type,multiple_choice|integer',
'number_answer' => 'required_if:type,number_input',
]);
$pts = [1=>5, 2=>10, 3=>20][$r->difficulty];
$q = Question::create([
'subject_id' => $r->subject_id,
'question_text' => $r->question_text,
'type' => $r->type,
'difficulty' => $r->difficulty,
'points_value' => $pts,
'active' => true,
]);
if ($r->type === 'multiple_choice') {
foreach ($r->options as $i => $text) {
if (trim($text) === '') continue;
AnswerOption::create([
'question_id' => $q->id,
'text' => $text,
'is_correct' => ($i == $r->correct),
'sort_order' => $i,
]);
}
} else {
AnswerOption::create([
'question_id' => $q->id,
'text' => $r->number_answer,
'is_correct' => true,
'sort_order' => 0,
]);
}
return redirect()->route('admin.questions.index')->with('success','Frage gespeichert.');
}
public function edit(Question $question) {
$subjects = Subject::all();
$question->load('answerOptions');
return view('admin.questions.edit', compact('question','subjects'));
}
public function update(Request $r, Question $question) {
$r->validate([
'subject_id' => 'required|exists:subjects,id',
'question_text' => 'required|string',
'difficulty' => 'required|in:1,2,3',
'options' => 'array',
'options.*' => 'string',
'correct' => 'integer',
]);
$pts = [1=>5, 2=>10, 3=>20][$r->difficulty];
$question->update([
'subject_id' => $r->subject_id,
'question_text' => $r->question_text,
'difficulty' => $r->difficulty,
'points_value' => $pts,
'active' => $r->boolean('active', true),
]);
if ($r->filled('options')) {
$question->answerOptions()->delete();
foreach ($r->options as $i => $text) {
if (trim($text) === '') continue;
AnswerOption::create([
'question_id' => $question->id,
'text' => $text,
'is_correct' => ($i == $r->correct),
'sort_order' => $i,
]);
}
}
return redirect()->route('admin.questions.index')->with('success','Gespeichert.');
}
public function destroy(Question $question) {
$question->delete();
return redirect()->route('admin.questions.index')->with('success','Frage gelöscht.');
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\RewardRedemption;
use Illuminate\Http\Request;
class RedemptionController extends Controller {
public function index() {
$pending = RewardRedemption::with(['user','reward'])->where('status','pending')->latest()->get();
$resolved = RewardRedemption::with(['user','reward','resolver'])->whereIn('status',['approved','rejected'])->latest()->take(50)->get();
return view('admin.redemptions.index', compact('pending','resolved'));
}
public function approve(RewardRedemption $redemption) {
if ($redemption->status !== 'pending') abort(422);
$redemption->update([
'status' => 'approved',
'resolved_at' => now(),
'resolved_by' => auth()->id(),
]);
return back()->with('success','Freigegeben!');
}
public function reject(Request $r, RewardRedemption $redemption) {
if ($redemption->status !== 'pending') abort(422);
$redemption->update([
'status' => 'rejected',
'note' => $r->note,
'resolved_at' => now(),
'resolved_by' => auth()->id(),
]);
// Punkte zurückgeben
$redemption->user->increment('points', $redemption->points_spent);
return back()->with('success','Abgelehnt, Punkte zurückgebucht.');
}
}
@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Reward;
use Illuminate\Http\Request;
class RewardController extends Controller {
public function index() {
$rewards = Reward::orderBy('points_cost')->get();
return view('admin.rewards.index', compact('rewards'));
}
public function create() { return view('admin.rewards.create'); }
public function store(Request $r) {
$r->validate([
'name' => 'required|string|max:80',
'description' => 'nullable|string|max:200',
'icon' => 'required|string|max:10',
'points_cost' => 'required|integer|min:1',
'minutes' => 'nullable|integer|min:1',
]);
Reward::create(array_merge($r->only('name','description','icon','points_cost','minutes'), ['active'=>true]));
return redirect()->route('admin.rewards.index')->with('success','Belohnung erstellt.');
}
public function edit(Reward $reward) { return view('admin.rewards.edit', compact('reward')); }
public function update(Request $r, Reward $reward) {
$r->validate([
'name' => 'required|string|max:80',
'description' => 'nullable|string|max:200',
'icon' => 'required|string|max:10',
'points_cost' => 'required|integer|min:1',
'minutes' => 'nullable|integer|min:1',
]);
$reward->update(array_merge(
$r->only('name','description','icon','points_cost','minutes'),
['active' => $r->boolean('active', true)]
));
return redirect()->route('admin.rewards.index')->with('success','Gespeichert.');
}
public function destroy(Reward $reward) {
$reward->delete();
return redirect()->route('admin.rewards.index')->with('success','Belohnung gelöscht.');
}
}
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller {
public function index() {
$users = User::where('role','child')->withCount('attempts')->orderBy('name')->get();
return view('admin.users.index', compact('users'));
}
public function create() { return view('admin.users.create'); }
public function store(Request $r) {
$r->validate([
'name' => 'required|string|max:60',
'email' => 'required|email|unique:users',
'password' => 'required|min:6',
]);
User::create([
'name' => $r->name,
'email' => $r->email,
'password' => Hash::make($r->password),
'role' => 'child',
'points' => 0,
]);
return redirect()->route('admin.users.index')->with('success','Kind-Konto erstellt.');
}
public function edit(User $user) { return view('admin.users.edit', compact('user')); }
public function update(Request $r, User $user) {
$r->validate([
'name' => 'required|string|max:60',
'email' => 'required|email|unique:users,email,'.$user->id,
'password' => 'nullable|min:6',
'points' => 'required|integer|min:0',
]);
$user->fill(['name'=>$r->name,'email'=>$r->email,'points'=>$r->points]);
if ($r->filled('password')) $user->password = Hash::make($r->password);
$user->save();
return redirect()->route('admin.users.index')->with('success','Gespeichert.');
}
public function destroy(User $user) {
$user->delete();
return redirect()->route('admin.users.index')->with('success','Konto gelöscht.');
}
}