update: for view and controller assessment add story text

This commit is contained in:
abiyasa05 2025-06-21 12:41:18 +07:00
parent ca3f0431ef
commit 468c167020
2 changed files with 117 additions and 219 deletions

View File

@ -88,7 +88,9 @@ public function show($id)
->with([ ->with([
'answers' => function ($query) use ($id) { 'answers' => function ($query) use ($id) {
$query->where('assessment_id', $id); $query->where('assessment_id', $id);
} },
'options',
'material.storyTexts'
]) ])
->get(); ->get();
@ -179,101 +181,6 @@ public function storeAnswer(Request $request, $assessmentId): JsonResponse
return response()->json(['success' => true]); return response()->json(['success' => true]);
} }
// public function submitAssessment($id)
// {
// try {
// $user = Auth::user();
// $assessment = LiteracyAssessment::where('id', $id)
// ->where('user_id', $user->id)
// ->firstOrFail();
// if ($assessment->status !== 'in_progress') {
// return response()->json(['error' => 'Asesmen tidak sedang berjalan.'], 400);
// }
// $answers = LiteracyAnswer::where('assessment_id', $id)->get();
// $correctWeight = 0;
// $totalWeight = 0;
// $essayThreshold = 60;
// $normalizeText = function ($text) {
// $text = strtolower($text);
// $text = preg_replace('/[^\p{L}\p{N}\s]/u', '', $text);
// $text = preg_replace('/\s+/', ' ', $text);
// return trim($text);
// };
// $feedback = [];
// foreach ($answers as $answer) {
// $question = LiteracyQuestion::find($answer->question_id);
// if (!$question)
// continue;
// $weight = $question->weight ?? ($question->type === 'essay' ? 5 : 1);
// $totalWeight += $weight;
// if ($question->type === 'multiple_choice') {
// if (
// $answer->option_id &&
// LiteracyOption::where('id', $answer->option_id)
// ->where('is_correct', true)
// ->exists()
// ) {
// $correctWeight += $weight;
// }
// } elseif ($question->type === 'essay') {
// $userAnswer = $normalizeText($answer->answer_text ?? '');
// $correctAnswerRaw = $question->essay_answer ?? '';
// $correctAnswers = array_map($normalizeText, preg_split('/\r\n|\r|\n/', $correctAnswerRaw));
// $maxMatchPercent = 0;
// foreach ($correctAnswers as $correctAnswer) {
// similar_text($userAnswer, $correctAnswer, $percent);
// $maxMatchPercent = max($maxMatchPercent, $percent);
// }
// if ($maxMatchPercent >= $essayThreshold) {
// $correctWeight += $weight;
// }
// }
// }
// // Hitung skor akhir berdasarkan bobot
// $score = $totalWeight > 0 ? ($correctWeight / $totalWeight) * 100 : 0;
// // Update asesmen yang sedang berjalan jadi completed
// $assessment->update([
// 'score' => round($score, 2),
// 'status' => 'completed',
// 'assessed_at' => now(),
// 'feedback' => json_encode($feedback), // Menyimpan feedback ke database
// ]);
// // Buat asesmen baru dengan status 'pending' untuk percakapan berikutnya
// LiteracyAssessment::create([
// 'user_id' => $user->id,
// 'status' => 'pending',
// 'score' => null,
// 'feedback' => '',
// 'assessed_at' => null,
// 'created_at' => now(),
// 'updated_at' => now(),
// ]);
// return response()->json([
// 'message' => 'Asesmen berhasil diselesaikan.',
// 'score' => round($score, 2),
// 'feedback' => $feedback, // Mengembalikan feedback dalam respons
// ], 200);
// } catch (\Exception $e) {
// return response()->json(['error' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
// }
// }
public function submitAssessment($id) public function submitAssessment($id)
{ {
try { try {
@ -338,7 +245,7 @@ public function submitAssessment($id)
$correctWeight += $weight; $correctWeight += $weight;
} else { } else {
try { try {
$apiResponse = Http::timeout(10)->post('http://127.0.0.1:8001/generate-feedback/', [ $apiResponse = Http::timeout(10)->post('http://127.0.0.1:8010/generate-feedback/', [
'user_answer' => $answer->answer_text, 'user_answer' => $answer->answer_text,
'expected_answer' => $bestMatch, 'expected_answer' => $bestMatch,
]); ]);

View File

@ -362,124 +362,126 @@
<!-- CONTENT DAN NAVIGASI SOAL --> <!-- CONTENT DAN NAVIGASI SOAL -->
@php @php
use Illuminate\Support\Str; use Illuminate\Support\Str;
$multipleChoiceQuestions = $questions->filter(fn($q) => $q->options->isNotEmpty()); $questionsByMaterial = $questions->groupBy('material_id');
$essayQuestions = $questions->filter(fn($q) => $q->options->isEmpty());
$multipleChoiceNumber = 0;
$essayNumber = 0;
@endphp @endphp
<main class="col-md-8"> <main class="col-md-8">
<div class="grid grid-cols-12 gap-6 p-6"> <div class="grid grid-cols-12 gap-6 p-6">
<div class="col-span-12 lg:col-span-9 space-y-4"> <div class="col-span-12 lg:col-span-9 space-y-4">
<!-- Bagian Pilihan Ganda -->
@if ($multipleChoiceQuestions->isNotEmpty()) {{-- ===================== PILIHAN GANDA ===================== --}}
<div class="row mt-4"> <h4 class="text-lg font-semibold mb-2">Soal Pilihan Ganda</h4>
<div class="col-12 d-flex justify-content-between align-items-center"> @php $mcGlobalNumber = 0; @endphp
<h4 class="text-base font-semibold mb-2">Bagian 1: Pilihan Ganda</h4> @foreach ($questionsByMaterial as $materialId => $materialQuestions)
<button onclick="window.close()" class="btn btn-outline-secondary btn-sm">Kembali</button> @php
$material = $materialQuestions->first()->material;
$storyTexts = $material->storyTexts ?? collect();
$multipleChoiceQuestions = $materialQuestions->where('type', 'multiple_choice');
@endphp
@if ($multipleChoiceQuestions->isNotEmpty())
<div class="mb-4">
<h5 class="font-semibold">Teks Bacaan: {{ $material->title }}</h5>
@foreach ($storyTexts as $text)
<div class="bg-gray-100 p-3 rounded mb-2">{{ $text->story_text }}</div>
@endforeach
</div> </div>
</div>
<p class="text-gray-500 text-sm mb-3">Pilih salah satu jawaban yang paling tepat.</p>
@foreach ($multipleChoiceQuestions as $question) @foreach ($multipleChoiceQuestions as $index => $question)
@php @php
$multipleChoiceNumber++; $mcGlobalNumber++;
$savedAnswer = optional($question->answers->where('assessment_id', $assessment->id)->first()); $savedAnswer = optional($question->answers->where('assessment_id', $assessment->id)->first());
$kutipan = null; $kutipan = null;
$pertanyaanBersih = $question->question_text; $pertanyaanBersih = $question->question_text;
// Deteksi apakah soal memuat kutipan atau paragraf if (Str::contains($question->question_text, 'Bacalah')) {
if (Str::contains($question->question_text, 'Bacalah')) { preg_match('/Bacalah (kutipan|paragraf) berikut:\s*\"(.*?)\"\s*(.*)/s', $question->question_text, $matches);
preg_match('/Bacalah (kutipan|paragraf) berikut:\s*"(.*?)"\s*(.*)/s', $question->question_text, $matches); if (count($matches) >= 4) {
if (count($matches) >= 4) { $kutipan = trim($matches[2]);
$kutipan = trim($matches[2]); $pertanyaanBersih = trim($matches[3]);
$pertanyaanBersih = trim($matches[3]); }
} }
} @endphp
@endphp
<div class="bg-white shadow-md rounded-lg p-4 border mb-4" id="question-mc-{{ $multipleChoiceNumber }}"> <div id="question-mc-{{ $mcGlobalNumber }}" class="bg-white shadow-md rounded-lg p-4 border mb-4">
<div class="row"> <div class="row">
<!-- Kolom Nomor Soal --> <div class="col-auto d-flex justify-content-start align-items-start pt-2">
<div class="col-auto d-flex justify-content-start align-items-start pt-2"> <strong>{{ $mcGlobalNumber }}.</strong>
<strong>{{ $multipleChoiceNumber }}.</strong> </div>
<div class="col">
@if ($kutipan)
<p class="mb-0 mt-2"><strong>Bacalah kutipan berikut:</strong></p>
<div class="ps-3 pe-3">
<em>"{{ $kutipan }}"</em>
</div>
@endif
<p class="mt-2 mb-0">{{ $pertanyaanBersih }}</p>
</div>
</div> </div>
<!-- Kolom Isi Soal -->
<div class="col"> <div class="mt-3">
@if ($kutipan) <div class="space-y-3">
<p class="mb-0 mt-2"><strong>Bacalah kutipan berikut:</strong></p> @foreach ($question->options as $option)
<div class="ps-3 pe-3"> <label class="d-block p-3 border rounded-sm cursor-pointer w-100">
<em>"{{ $kutipan }}"</em> <input type="radio" name="question_{{ $question->id }}"
</div> value="{{ $option->id }}" class="form-radio"
@endif onchange="saveAnswer('{{ $question->id }}', '{{ $option->id }}'); updateQuestionUI('mc', '{{ $question->id }}');"
<p class="mt-2 mb-0">{{ $pertanyaanBersih }}</p> {{ $savedAnswer && $savedAnswer->option_id == $option->id ? 'checked' : '' }}>
<span>{{ $option->option_text }}</span>
</label>
@endforeach
</div>
</div> </div>
</div> </div>
@endforeach
@endif
@endforeach
<div class="mt-3"> {{-- ===================== ISIAN / ESSAY ===================== --}}
<div class="space-y-3"> <h4 class="text-lg font-semibold mt-6 mb-2">Soal Isian</h4>
@foreach ($question->options as $option) @php $essayGlobalNumber = 0; @endphp
<label class="d-block p-3 border rounded-sm cursor-pointer w-100"> @foreach ($questionsByMaterial as $materialId => $materialQuestions)
<input type="radio" name="question_{{ $question->id }}" @php
value="{{ $option->id }}" class="form-radio" $material = $materialQuestions->first()->material;
onchange="saveAnswer('{{ $question->id }}', '{{ $option->id }}'); updateQuestionUI('mc', '{{ $question->id }}');" $storyTexts = $material->storyTexts ?? collect();
{{ $savedAnswer && $savedAnswer->option_id == $option->id ? 'checked' : '' }}> $essayQuestions = $materialQuestions->where('type', 'essay');
<span>{{ $option->option_text }}</span> @endphp
</label>
@endforeach @if ($essayQuestions->isNotEmpty())
</div> <div class="mb-4">
</div> <h5 class="font-semibold">Teks Bacaan: {{ $material->title }}</h5>
@foreach ($storyTexts as $text)
<div class="bg-gray-100 p-3 rounded mb-2">{{ $text->story_text }}</div>
@endforeach
</div> </div>
@endforeach
@endif
<!-- Bagian Isian / Essay --> @foreach ($essayQuestions as $index => $question)
@if ($essayQuestions->isNotEmpty()) @php
<h4 class="text-base font-semibold mt-4">Bagian 2: Isian</h4> $essayGlobalNumber++;
<p class="text-gray-500 text-sm mb-3">Jawablah pertanyaan berikut dengan jawaban yang sesuai.</p> $savedAnswer = optional($question->answers->where('assessment_id', $assessment->id)->first());
@endphp
@foreach ($essayQuestions as $question) <div id="question-essay-{{ $essayGlobalNumber }}" class="bg-white shadow-md rounded-lg p-4 border mb-4">
@php <div class="row">
$essayNumber++; <div class="col-auto d-flex justify-content-start align-items-start pt-2">
$savedAnswer = optional($question->answers->where('assessment_id', $assessment->id)->first()); <strong>{{ $essayGlobalNumber }}.</strong>
$kutipan = null; </div>
$pertanyaanBersih = $question->question_text; <div class="col">
<p class="mt-2 mb-0">{{ $question->question_text }}</p>
if (Str::contains($question->question_text, 'Bacalah')) { </div>
preg_match('/Bacalah (kutipan|paragraf) berikut:\s*"(.*?)"\s*(.*)/s', $question->question_text, $matches);
if (count($matches) >= 4) {
$kutipan = trim($matches[2]);
$pertanyaanBersih = trim($matches[3]);
}
}
@endphp
<div class="bg-white shadow-md rounded-lg p-4 border mb-4" id="question-essay-{{ $essayNumber }}">
<div class="row">
<!-- Kolom Nomor Soal -->
<div class="col-auto d-flex justify-content-start align-items-start pt-2">
<strong>{{ $essayNumber }}.</strong>
</div> </div>
<!-- Kolom Isi Soal -->
<div class="col"> <div class="mt-3">
@if ($kutipan) <textarea name="question_{{ $question->id }}"
<p class="mb-0 mt-2"><strong>Bacalah kutipan berikut:</strong></p> class="w-100 h-24 p-3 border rounded-lg focus:ring focus:ring-blue-200"
<div class="ps-3 pe-3"> placeholder="Masukkan jawaban Anda di sini..."
<em>"{{ $kutipan }}"</em> oninput="saveEssayAnswerDebounced('{{ $question->id }}', this.value); updateQuestionUI('essay', '{{ $question->id }}');">{{ $savedAnswer ? $savedAnswer->answer_text : '' }}</textarea>
</div>
@endif
<p class="mt-2 mb-0">{{ $pertanyaanBersih }}</p>
</div> </div>
</div> </div>
<div class="mt-3"> @endforeach
<textarea name="question_{{ $question->id }}" @endif
class="w-100 h-24 p-3 border rounded-lg focus:ring focus:ring-blue-200" @endforeach
placeholder="Masukkan jawaban Anda di sini..."
oninput="saveEssayAnswerDebounced('{{ $question->id }}', this.value); updateQuestionUI('essay', '{{ $question->id }}');">{{ $savedAnswer ? $savedAnswer->answer_text : '' }}</textarea>
</div>
</div>
@endforeach
@endif
</div> </div>
</div> </div>
</main> </main>
@ -654,27 +656,25 @@ class="btn {{ $btnClass }} d-flex justify-content-center align-items-center"
function checkUnansweredQuestions() { function checkUnansweredQuestions() {
let unansweredCount = 0; let unansweredCount = 0;
// Cek soal pilihan ganda (radio button) @foreach ($questions as $question)
@foreach ($multipleChoiceQuestions as $question) @if ($question->type === 'multiple_choice')
if (!$("input[name='question_{{ $question->id }}']:checked").val()) { if (!$("input[name='question_{{ $question->id }}']:checked").val()) {
unansweredCount++; unansweredCount++;
} }
@elseif ($question->type === 'essay')
if (!$("textarea[name='question_{{ $question->id }}']").val().trim()) {
unansweredCount++;
}
@endif
@endforeach @endforeach
// Cek soal isian (textarea) console.log("Soal yang belum dijawab:", unansweredCount);
@foreach ($essayQuestions as $question)
if (!$("textarea[name='question_{{ $question->id }}']").val().trim()) {
unansweredCount++;
}
@endforeach
console.log("Soal yang belum dijawab:", unansweredCount); // Debugging
if (unansweredCount > 0) { if (unansweredCount > 0) {
$("#unansweredCount").text(unansweredCount); $("#unansweredCount").text(unansweredCount);
$("#unansweredWarningModal").modal("show"); // Tampilkan modal peringatan $("#unansweredWarningModal").modal("show");
} else { } else {
$("#confirmSubmitModal").modal("show"); // Tampilkan modal konfirmasi $("#confirmSubmitModal").modal("show");
} }
} }
</script> </script>
@ -705,15 +705,6 @@ function checkUnansweredQuestions() {
console.error("Gagal menyimpan jawaban:", error); console.error("Gagal menyimpan jawaban:", error);
} }
} }
// function updateNavigationButton(questionId) {
// const navButton = document.querySelector(`button[data-question-id='${questionId}']`);
// if (navButton) {
// navButton.classList.remove("btn-outline-secondary");
// navButton.classList.add("btn-primary", "border-white");
// }
// }
</script> </script>
<script> <script>