Files
eCert-MBIP/app/Http/Controllers/Public/QuestionnaireController.php
2026-05-19 09:53:36 +08:00

132 lines
4.8 KiB
PHP

<?php
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Models\Participant;
use App\Models\ProgramQrCode;
use App\Models\QuestionnaireResponse;
use App\Models\QuestionnaireAnswer;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\View\View;
class QuestionnaireController extends Controller
{
public function show(string $qr_token, string $participant_uuid): View|RedirectResponse
{
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
$program = $qrCode->program;
$participant = Participant::where('uuid', $participant_uuid)->firstOrFail();
$pp = $program->programParticipants()->where('participant_id', $participant->id)->first();
abort_if(! $pp, 404);
$pq = $program->questionnaire()->with('questionnaireSet')->first();
if (! $pq || ! $pq->is_confirmed) {
return redirect()->route('public.semak.show', $qr_token);
}
if (QuestionnaireResponse::where('program_id', $program->id)->where('participant_id', $participant->id)->exists()) {
return view('public.questionnaire.already', compact('program', 'participant', 'qrCode'));
}
$questions = $this->loadHierarchical($pq);
return view('public.questionnaire.show', compact('program', 'participant', 'qrCode', 'pq', 'questions'));
}
public function submit(Request $request, string $qr_token, string $participant_uuid): View|RedirectResponse
{
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
$program = $qrCode->program;
$participant = Participant::where('uuid', $participant_uuid)->firstOrFail();
$pp = $program->programParticipants()->where('participant_id', $participant->id)->first();
abort_if(! $pp, 404);
$pq = $program->questionnaire()->with('questionnaireSet')->first();
abort_if(! $pq || ! $pq->is_confirmed, 404);
if (QuestionnaireResponse::where('program_id', $program->id)->where('participant_id', $participant->id)->exists()) {
return view('public.questionnaire.already', compact('program', 'participant', 'qrCode'));
}
$questions = $this->loadHierarchical($pq);
$answerable = $this->flatten($questions);
$rules = [];
foreach ($answerable as $q) {
if ($q->question_type === 'multiple_choice') {
$rules['q_' . $q->id] = ($q->is_required ? 'required|' : 'nullable|') . 'array';
} else {
$rules['q_' . $q->id] = $q->is_required ? 'required' : 'nullable';
}
}
$request->validate($rules, ['q_*.required' => 'Soalan ini wajib dijawab.']);
$response = QuestionnaireResponse::create([
'program_id' => $program->id,
'participant_id' => $participant->id,
'questionnaire_set_id' => $pq->questionnaire_set_id,
'submitted_at' => now(),
'ip_address' => $request->ip(),
'user_agent' => substr($request->userAgent() ?? '', 0, 500),
]);
foreach ($answerable as $q) {
$raw = $request->input('q_' . $q->id);
if ($raw === null && ! $q->is_required) {
continue;
}
$value = match ($q->question_type) {
'multiple_choice' => (array) $raw,
'rating' => (int) $raw,
default => $raw,
};
QuestionnaireAnswer::create([
'questionnaire_response_id' => $response->id,
'questionnaire_question_id' => $q->id,
'answer_value' => $value,
]);
}
return view('public.questionnaire.thankyou', compact('program', 'participant', 'qrCode'));
}
// ── Helpers ──────────────────────────────────────────────────────────────
private function loadHierarchical($pq): Collection
{
return $pq->questionnaireSet->questions()
->whereNull('parent_id')
->with(['children' => fn($q) => $q->orderBy('sort_order')])
->orderBy('sort_order')
->get();
}
/** Return only answerable (non-tajuk) questions as a flat collection. */
private function flatten(Collection $topLevel): Collection
{
$out = collect();
foreach ($topLevel as $q) {
if ($q->question_type === 'tajuk') {
foreach ($q->children as $child) {
$out->push($child);
}
} else {
$out->push($q);
}
}
return $out;
}
}