'tasks'])] #[Lazy] class Run extends Component { public string $slug = ''; public array $task = []; public array $questions = []; public int $currentStep = 1; public int $totalSteps = 0; public bool $finished = false; public int $correctAnswers = 0; /** Last answer payload — varies by question type: * - choice : ['type'=>'choice', 'index'=>int] * - cloze : ['type'=>'cloze', 'text'=>string] * - geo-map : ['type'=>'geo-map','region'=>string] */ public ?array $lastAnswer = null; public ?bool $lastAnswerCorrect = null; /** Active typed input for cloze questions (wire:model). */ public string $clozeInput = ''; /** Lerni AI helper panel — toggled via showLerni; can be shared via URL (?lerni=1). */ #[Url(as: 'lerni', keep: false)] public bool $showLerni = false; public function mount(string $slug): void { $task = $this->findTask($slug); abort_if($task === null, 404, "Task '{$slug}' not found."); $this->slug = $slug; $this->task = $task; $this->questions = config("task-questions.{$slug}", []); $this->totalSteps = count($this->questions); abort_if($this->totalSteps === 0, 404, "No questions configured for task '{$slug}'."); } public static function placeholder(array $params = []): View { return view('livewire.tasks.run-placeholder'); } public function currentQuestion(): array { $q = $this->questions[$this->currentStep - 1] ?? $this->questions[0]; // Default type for legacy entries $q['type'] = $q['type'] ?? 'choice'; return $q; } public function submitChoice(int $optionIndex): void { $q = $this->currentQuestion(); if (($q['type'] ?? 'choice') !== 'choice') { return; } $isCorrect = $optionIndex === ($q['correct'] ?? -1); $this->lastAnswer = ['type' => 'choice', 'index' => $optionIndex]; $this->lastAnswerCorrect = $isCorrect; if ($isCorrect) { $this->correctAnswers++; } } public function submitCloze(): void { $q = $this->currentQuestion(); if (($q['type'] ?? null) !== 'cloze') { return; } $given = trim(mb_strtolower($this->clozeInput)); $expected = trim(mb_strtolower((string) ($q['correct'] ?? ''))); $accept = array_map(fn($a) => trim(mb_strtolower((string) $a)), $q['accept'] ?? []); $isCorrect = $given !== '' && ($given === $expected || in_array($given, $accept, true)); $this->lastAnswer = ['type' => 'cloze', 'text' => $this->clozeInput]; $this->lastAnswerCorrect = $isCorrect; if ($isCorrect) { $this->correctAnswers++; } } public function submitRegion(string $region): void { $q = $this->currentQuestion(); if (($q['type'] ?? null) !== 'geo-map') { return; } $isCorrect = $region === ($q['correct'] ?? ''); $this->lastAnswer = ['type' => 'geo-map', 'region' => $region]; $this->lastAnswerCorrect = $isCorrect; if ($isCorrect) { $this->correctAnswers++; } } public function nextStep(): void { if ($this->currentStep >= $this->totalSteps) { $this->finished = true; return; } $this->currentStep++; $this->lastAnswer = null; $this->lastAnswerCorrect = null; $this->clozeInput = ''; $this->showLerni = false; } public function toggleLerni(): void { $this->showLerni = ! $this->showLerni; } public function restart(): void { $this->currentStep = 1; $this->finished = false; $this->correctAnswers = 0; $this->lastAnswer = null; $this->lastAnswerCorrect = null; $this->clozeInput = ''; $this->showLerni = false; } protected function findTask(string $slug): ?array { return collect(config('tasks', []))->firstWhere('slug', $slug); } public function render(): View { return view('livewire.tasks.run'); } }