done halaman lms(-token jwt)
This commit is contained in:
parent
c647e3cfe1
commit
eba04dd44b
@ -2,16 +2,182 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Jawaban;
|
||||||
|
use App\Models\JawabanDetail;
|
||||||
use App\Models\Soal;
|
use App\Models\Soal;
|
||||||
|
use App\Models\SoalDetail;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class SoalController extends Controller
|
class SoalController extends Controller
|
||||||
{
|
{
|
||||||
public function index(){
|
public function index(Request $request)
|
||||||
$soal = Soal::first();
|
{
|
||||||
$payload = [
|
$daftarSoal = Soal::orderBy('judul_soal')->get();
|
||||||
'soal' => $soal
|
|
||||||
];
|
if ($daftarSoal->isEmpty()) {
|
||||||
return view('soal.index', $payload);
|
abort(404, 'Data soal tidak ditemukan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$selectedSoalId = (int) $request->query('soal_id', $daftarSoal->first()->id);
|
||||||
|
|
||||||
|
if (!$request->boolean('start')) {
|
||||||
|
return view('soal.list', [
|
||||||
|
'daftarSoal' => $daftarSoal,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$soal = Soal::with('soalDetail')->find($selectedSoalId);
|
||||||
|
|
||||||
|
if (!$soal) {
|
||||||
|
abort(404, 'Data soal tidak ditemukan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$detailSoal = $soal->soalDetail()
|
||||||
|
->orderBy('hal')
|
||||||
|
->orderBy('id')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$daftarHal = $detailSoal->pluck('hal')
|
||||||
|
->map(function ($value) {
|
||||||
|
return is_null($value) ? null : (int) $value;
|
||||||
|
})
|
||||||
|
->filter(function ($value) {
|
||||||
|
return !is_null($value);
|
||||||
|
})
|
||||||
|
->unique()
|
||||||
|
->sort()
|
||||||
|
->values();
|
||||||
|
|
||||||
|
if ($daftarHal->isEmpty()) {
|
||||||
|
$daftarHal = collect([1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hal = (int) $request->query('hal', $daftarHal->first());
|
||||||
|
|
||||||
|
if (!$daftarHal->contains($hal)) {
|
||||||
|
$hal = $daftarHal->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$detailSoal = $detailSoal->map(function ($detail) use ($daftarHal) {
|
||||||
|
$detail->hal = $detail->hal ?? $daftarHal->first();
|
||||||
|
return $detail;
|
||||||
|
});
|
||||||
|
|
||||||
|
$soal->setRelation('soalDetail', $detailSoal);
|
||||||
|
|
||||||
|
$halPertama = $daftarHal->first() ?? $hal;
|
||||||
|
|
||||||
|
return view('soal.index', [
|
||||||
|
'soal' => $soal,
|
||||||
|
'hal' => $hal,
|
||||||
|
'halPertama' => $halPertama,
|
||||||
|
'daftarHal' => $daftarHal,
|
||||||
|
'totalHal' => $daftarHal->count(),
|
||||||
|
'soalId' => $soal->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'lms_mutu_soal_id' => 'required|integer',
|
||||||
|
'hal' => 'nullable|integer|min:1',
|
||||||
|
'jawaban' => 'required|array',
|
||||||
|
'jawaban.*' => 'required',
|
||||||
|
'jawaban_lainnya' => 'nullable|array',
|
||||||
|
'jawaban_lainnya.*' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$validator->after(function ($validator) use ($request) {
|
||||||
|
$jawabanUtama = $request->input('jawaban', []);
|
||||||
|
$jawabanLainnya = $request->input('jawaban_lainnya', []);
|
||||||
|
|
||||||
|
foreach ($jawabanUtama as $detailId => $answer) {
|
||||||
|
if ($answer === 'Lainnya') {
|
||||||
|
$jawabanIsian = $jawabanLainnya[$detailId] ?? null;
|
||||||
|
if (!$jawabanIsian) {
|
||||||
|
$validator->errors()->add("jawaban_lainnya.$detailId", 'Jawaban lainnya wajib diisi.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$validated = $validator->validate();
|
||||||
|
|
||||||
|
$detailIds = array_map('intval', array_keys($validated['jawaban']));
|
||||||
|
$detailMeta = SoalDetail::whereIn('id', $detailIds)
|
||||||
|
->get(['id', 'soal'])
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$jawabanBaru = null;
|
||||||
|
$jawabanLainnya = $validated['jawaban_lainnya'] ?? [];
|
||||||
|
$namaResponden = null;
|
||||||
|
$unitKerja = null;
|
||||||
|
|
||||||
|
DB::connection('dbLmsMutu')->transaction(function () use (&$jawabanBaru, $validated, $jawabanLainnya, $detailMeta, &$namaResponden, &$unitKerja) {
|
||||||
|
$jawabanBaru = Jawaban::create([
|
||||||
|
'lms_mutu_soal_id' => $validated['lms_mutu_soal_id'],
|
||||||
|
'pegawai_id' => 1,
|
||||||
|
'tanggal_isi' => Carbon::now()->addHour(7),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($validated['jawaban'] as $detailId => $answer) {
|
||||||
|
if (is_array($answer)) {
|
||||||
|
$answer = json_encode($answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($answer === 'Lainnya' && !empty($jawabanLainnya[$detailId])) {
|
||||||
|
$answer = $jawabanLainnya[$detailId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($answer === null || $answer === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$detailIdInt = (int) $detailId;
|
||||||
|
$pertanyaan = null;
|
||||||
|
if ($detailMeta->has($detailIdInt)) {
|
||||||
|
$decoded = json_decode($detailMeta[$detailIdInt]->soal, true);
|
||||||
|
$pertanyaan = Str::lower(trim($decoded['soal'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pertanyaan) {
|
||||||
|
if (Str::contains($pertanyaan, 'nama/inisial responden')) {
|
||||||
|
$namaResponden = $answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::contains($pertanyaan, 'unit/area kerja anda saat ini')) {
|
||||||
|
$unitKerja = $answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JawabanDetail::create([
|
||||||
|
'lms_mutu_jawaban_id' => $jawabanBaru->id,
|
||||||
|
'lms_mutu_soal_detail_id' => $detailIdInt,
|
||||||
|
'jawaban' => $answer,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatePayload = [];
|
||||||
|
|
||||||
|
if (!empty($namaResponden)) {
|
||||||
|
$updatePayload['nama'] = $namaResponden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($unitKerja)) {
|
||||||
|
$updatePayload['unit'] = $unitKerja;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($updatePayload)) {
|
||||||
|
$jawabanBaru->update($updatePayload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return redirect('/')
|
||||||
|
->with('success', 'Jawaban berhasil disimpan.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,8 @@ class Jawaban extends Model
|
|||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
protected $with = ['jawabanDetail'];
|
protected $with = ['jawabanDetail'];
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
public function jawabanDetail(){
|
public function jawabanDetail(){
|
||||||
return $this->hasMany(JawabanDetail::class, 'lms_mutu_jawaban_id', 'id');
|
return $this->hasMany(JawabanDetail::class, 'lms_mutu_jawaban_id', 'id');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,4 +9,5 @@ class JawabanDetail extends Model
|
|||||||
protected $connection = 'dbLmsMutu';
|
protected $connection = 'dbLmsMutu';
|
||||||
protected $table = 'public.lms_mutu_jawaban_detail';
|
protected $table = 'public.lms_mutu_jawaban_detail';
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
public $timestamps = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
|
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
|
||||||
<!--? Template customizer: To hide customizer set displayCustomizer value false in config.js. -->
|
<!--? Template customizer: To hide customizer set displayCustomizer value false in config.js. -->
|
||||||
|
|
||||||
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
|
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
|
||||||
<script src="{{ asset('vuexy/assets/js/config.js') }}"></script>
|
<script src="{{ asset('vuexy/assets/js/config.js') }}"></script>
|
||||||
@yield('custom_css')
|
@yield('custom_css')
|
||||||
@ -96,7 +96,7 @@
|
|||||||
Authorization: `bearer ${token}`,
|
Authorization: `bearer ${token}`,
|
||||||
"x-klien": 1
|
"x-klien": 1
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
setNameAccount();
|
setNameAccount();
|
||||||
$('#btn_logout').click(function(){
|
$('#btn_logout').click(function(){
|
||||||
@ -114,7 +114,7 @@
|
|||||||
buttonsStyling: false
|
buttonsStyling: false
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
if (result.value) {
|
if (result.value) {
|
||||||
$.removeCookie('token');
|
$.removeCookie('token');
|
||||||
window.location.href = "/logout";
|
window.location.href = "/logout";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -129,17 +129,17 @@
|
|||||||
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
|
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
}).join(''));
|
}).join(''));
|
||||||
|
|
||||||
return JSON.parse(jsonPayload);
|
return JSON.parse(jsonPayload);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error("Invalid JWT:", e);
|
// console.error("Invalid JWT:", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNameAccount() {
|
function setNameAccount() {
|
||||||
const decode = parseJwt();
|
const decode = parseJwt();
|
||||||
|
|
||||||
if(decode){
|
if(decode){
|
||||||
$('#name_account').html(decode.nama_pegawai);
|
$('#name_account').html(decode.nama_pegawai);
|
||||||
}
|
}
|
||||||
@ -148,4 +148,4 @@
|
|||||||
@yield('custom_js')
|
@yield('custom_js')
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,4 +1,452 @@
|
|||||||
@extends('layouts.template')
|
@extends('layouts.template')
|
||||||
@section('content')
|
|
||||||
|
|
||||||
|
@section('title', 'Kuesioner Soal')
|
||||||
|
|
||||||
|
@section('custom_css')
|
||||||
|
<style>
|
||||||
|
.question-card {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-scroll {
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px dashed #ced4da;
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-scroll::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(13, 110, 253, 0.4);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lainnya-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lainnya-input.show {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@php
|
||||||
|
$listHal = $daftarHal ?? collect([$hal]);
|
||||||
|
$posisiAktif = $listHal->search($hal);
|
||||||
|
if ($posisiAktif === false) {
|
||||||
|
$posisiAktif = 0;
|
||||||
|
}
|
||||||
|
$totalHalaman = $totalHal ?? $listHal->count();
|
||||||
|
$progressPercentage = $totalHalaman > 0 ? round((($posisiAktif + 1) / max($totalHalaman, 1)) * 100) : 100;
|
||||||
|
$halSebelumnya = $posisiAktif > 0 ? $listHal[$posisiAktif - 1] : null;
|
||||||
|
$halBerikut = ($posisiAktif < $listHal->count() - 1) ? $listHal[$posisiAktif + 1] : null;
|
||||||
|
$isHalTerakhir = $halBerikut === null;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-xl-8 col-lg-10">
|
||||||
|
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mb-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-muted mb-1">Kuesioner</p>
|
||||||
|
<h3 class="fw-semibold mb-0">{{ $soal->judul_soal ?? 'Daftar Soal' }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<a href="{{ route('soal.index') }}" class="btn btn-outline-secondary btn-sm">
|
||||||
|
← Daftar Judul
|
||||||
|
</a>
|
||||||
|
<span class="badge bg-label-primary fs-6 px-3 py-2" id="badge-hal">Halaman {{ $hal }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between small text-muted mb-1">
|
||||||
|
<span>Progres Halaman</span>
|
||||||
|
<span id="progress-text">{{ $progressPercentage }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 8px;">
|
||||||
|
<div class="progress-bar" id="progress-bar" role="progressbar" style="width: {{ $progressPercentage }}%;" aria-valuenow="{{ $progressPercentage }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($hal === $halPertama)
|
||||||
|
<div class="card border-0 shadow-sm mb-4" id="head_soal">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted mb-1">Judul Soal</p>
|
||||||
|
<h5 class="mb-3 text-primary">{{ $soal->judul_soal ?? '-' }}</h5>
|
||||||
|
<p class="text-muted mb-2">Keterangan</p>
|
||||||
|
<div class="border rounded p-3 bg-light text-body">
|
||||||
|
{!! $soal->keterangan_soal !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<strong>Terjadi kesalahan.</strong> Silakan periksa kembali jawaban Anda.
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||||
|
<h5 class="mb-0">Daftar Pertanyaan</h5>
|
||||||
|
<small class="text-muted" id="summary-hal">Halaman {{ $hal }} dari {{ $listHal->count() }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="form-soal" method="POST" action="{{ route('soal.store') }}" data-hal-list='@json($listHal->values())'>
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="lms_mutu_soal_id" value="{{ $soal->id }}">
|
||||||
|
<input type="hidden" name="hal" id="input-hal" value="{{ $hal }}">
|
||||||
|
|
||||||
|
@forelse ($soal->soalDetail as $detail)
|
||||||
|
@php
|
||||||
|
$detailConfig = json_decode($detail->soal, true) ?? [];
|
||||||
|
$pertanyaan = $detailConfig['soal'] ?? 'Pertanyaan tidak tersedia';
|
||||||
|
$type = $detailConfig['type'] ?? 'option';
|
||||||
|
$options = $detailConfig['options'] ?? [];
|
||||||
|
$oldAnswer = old('jawaban.' . $detail->id);
|
||||||
|
$oldOtherAnswer = old('jawaban_lainnya.' . $detail->id);
|
||||||
|
$showLainnya = $oldAnswer === 'Lainnya' || (!empty($oldOtherAnswer));
|
||||||
|
$detailHal = $detail->hal ?? $listHal->first();
|
||||||
|
$isVisible = $detailHal == $hal;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="question-card mb-4"
|
||||||
|
data-hal-card="{{ $detailHal }}"
|
||||||
|
style="{{ $isVisible ? '' : 'display: none;' }}">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-2 flex-wrap gap-2">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="badge rounded-pill bg-label-primary fs-6">{{ $loop->iteration }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-semibold mb-3">{{ $pertanyaan }}</h5>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@if ($type === 'textarea')
|
||||||
|
<textarea class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
name="jawaban[{{ $detail->id }}]" rows="4" required
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
placeholder="Tulis jawaban Anda di sini">{{ old('jawaban.' . $detail->id) }}</textarea>
|
||||||
|
@elseif ($type === 'text')
|
||||||
|
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
name="jawaban[{{ $detail->id }}]" value="{{ old('jawaban.' . $detail->id) }}" required
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
placeholder="Masukkan jawaban Anda">
|
||||||
|
@else
|
||||||
|
@if (!empty($options))
|
||||||
|
<div class="option-scroll">
|
||||||
|
@foreach ($options as $optionIndex => $option)
|
||||||
|
@php
|
||||||
|
$optionId = 'jawaban-' . $detail->id . '-' . $optionIndex;
|
||||||
|
$isLainnya = strtolower(trim($option)) === 'lainnya';
|
||||||
|
@endphp
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
type="radio"
|
||||||
|
name="jawaban[{{ $detail->id }}]"
|
||||||
|
id="{{ $optionId }}"
|
||||||
|
value="{{ $option }}"
|
||||||
|
data-lainnya-radio="{{ $isLainnya ? $detail->id : '' }}" required
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
{{ $oldAnswer === $option ? 'checked' : '' }}>
|
||||||
|
<label class="form-check-label" for="{{ $optionId }}">
|
||||||
|
{{ $option }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@if ($isLainnya)
|
||||||
|
<div class="lainnya-input {{ $showLainnya ? 'show' : '' }}" data-lainnya-wrapper="{{ $detail->id }}">
|
||||||
|
<input type="text" class="form-control form-control-sm mt-2"
|
||||||
|
name="jawaban_lainnya[{{ $detail->id }}]"
|
||||||
|
value="{{ $oldOtherAnswer }}"
|
||||||
|
data-lainnya-input="{{ $detail->id }}"
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
placeholder="Tuliskan jawaban lainnya"
|
||||||
|
{{ $showLainnya ? 'required' : '' }}>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
name="jawaban[{{ $detail->id }}]" value="{{ old('jawaban.' . $detail->id) }}" required
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
placeholder="Masukkan jawaban Anda">
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@error('jawaban.' . $detail->id)
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
@error('jawaban_lainnya.' . $detail->id)
|
||||||
|
<div class="text-danger small mt-1">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Belum ada pertanyaan pada halaman ini.
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
@error('lms_mutu_soal_id')
|
||||||
|
<p class="text-danger mb-0">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
@error('jawaban')
|
||||||
|
<p class="text-danger mb-0">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
|
||||||
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mt-4 gap-3">
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary btn-prev-hal"
|
||||||
|
data-nav-control="prev"
|
||||||
|
{{ $halSebelumnya ? '' : 'disabled' }}>
|
||||||
|
← Halaman Sebelumnya
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary btn-next-hal"
|
||||||
|
data-nav-control="next"
|
||||||
|
{{ $halBerikut ? '' : 'disabled' }}>
|
||||||
|
Halaman Berikutnya →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary ms-md-auto"
|
||||||
|
data-final-submit="true"
|
||||||
|
style="{{ $isHalTerakhir ? '' : 'display: none;' }}"
|
||||||
|
{{ $soal->soalDetail->isEmpty() ? 'disabled' : '' }}>
|
||||||
|
Simpan Jawaban
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('custom_js')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const form = document.getElementById('form-soal');
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let halList;
|
||||||
|
try {
|
||||||
|
halList = JSON.parse(form.dataset.halList || '[]');
|
||||||
|
} catch (error) {
|
||||||
|
halList = [];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(halList) || !halList.length) {
|
||||||
|
halList = [parseInt(document.getElementById('input-hal')?.value || '1', 10) || 1];
|
||||||
|
}
|
||||||
|
halList = halList.map(function (value) {
|
||||||
|
return parseInt(value, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hiddenHalInput = document.getElementById('input-hal');
|
||||||
|
let currentHal = parseInt(hiddenHalInput?.value || halList[0], 10);
|
||||||
|
const navHalButtons = document.querySelectorAll('.nav-hal-btn');
|
||||||
|
const questionCards = document.querySelectorAll('[data-hal-card]');
|
||||||
|
const halInputs = document.querySelectorAll('[data-field-hal]');
|
||||||
|
const prevButton = document.querySelector('.btn-prev-hal');
|
||||||
|
const nextButton = document.querySelector('.btn-next-hal');
|
||||||
|
const finalSubmitButton = document.querySelector('[data-final-submit="true"]');
|
||||||
|
const badgeHal = document.getElementById('badge-hal');
|
||||||
|
const progressBar = document.getElementById('progress-bar');
|
||||||
|
const progressText = document.getElementById('progress-text');
|
||||||
|
const summaryHal = document.getElementById('summary-hal');
|
||||||
|
const totalHal = halList.length || 1;
|
||||||
|
|
||||||
|
setupLainnyaInputs();
|
||||||
|
updateQuestionVisibility();
|
||||||
|
updateNavigationUI();
|
||||||
|
|
||||||
|
navHalButtons.forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const targetHal = parseInt(button.dataset.navHal, 10);
|
||||||
|
changeHal(targetHal);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prevButton) {
|
||||||
|
prevButton.addEventListener('click', function () {
|
||||||
|
navigateRelative(-1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.addEventListener('click', function () {
|
||||||
|
navigateRelative(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateRelative(step) {
|
||||||
|
const currentIndex = halList.indexOf(currentHal);
|
||||||
|
const targetHal = halList[currentIndex + step];
|
||||||
|
if (typeof targetHal === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(targetHal === 1){
|
||||||
|
document.getElementById('head_soal').classList.remove('d-none')
|
||||||
|
}else{
|
||||||
|
document.getElementById('head_soal').classList.add('d-none')
|
||||||
|
}
|
||||||
|
changeHal(targetHal);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeHal(targetHal) {
|
||||||
|
if (targetHal === currentHal || halList.indexOf(targetHal) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentIndex = halList.indexOf(currentHal);
|
||||||
|
const targetIndex = halList.indexOf(targetHal);
|
||||||
|
const movingForward = targetIndex > currentIndex;
|
||||||
|
|
||||||
|
if (movingForward && !validateHal(currentHal)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHal = targetHal;
|
||||||
|
if (hiddenHalInput) {
|
||||||
|
hiddenHalInput.value = currentHal;
|
||||||
|
}
|
||||||
|
updateQuestionVisibility();
|
||||||
|
updateNavigationUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateHal(halValue) {
|
||||||
|
const fieldsToDisable = [];
|
||||||
|
halInputs.forEach(function (field) {
|
||||||
|
const fieldHal = parseInt(field.dataset.fieldHal, 10);
|
||||||
|
if (fieldHal !== halValue && field.required) {
|
||||||
|
field.dataset.tmpRequired = '1';
|
||||||
|
field.required = false;
|
||||||
|
fieldsToDisable.push(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValid = form.checkValidity();
|
||||||
|
if (!isValid) {
|
||||||
|
form.reportValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsToDisable.forEach(function (field) {
|
||||||
|
field.required = true;
|
||||||
|
delete field.dataset.tmpRequired;
|
||||||
|
});
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQuestionVisibility() {
|
||||||
|
questionCards.forEach(function (card) {
|
||||||
|
const halValue = parseInt(card.dataset.halCard, 10);
|
||||||
|
card.style.display = halValue === currentHal ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNavigationUI() {
|
||||||
|
const currentIndex = halList.indexOf(currentHal);
|
||||||
|
const isFirst = currentIndex <= 0;
|
||||||
|
const isLast = currentIndex === halList.length - 1;
|
||||||
|
|
||||||
|
if (prevButton) {
|
||||||
|
prevButton.disabled = isFirst;
|
||||||
|
}
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.disabled = isLast;
|
||||||
|
}
|
||||||
|
|
||||||
|
navHalButtons.forEach(function (button) {
|
||||||
|
if (parseInt(button.dataset.navHal, 10) === currentHal) {
|
||||||
|
button.classList.remove('btn-outline-primary');
|
||||||
|
button.classList.add('btn-primary');
|
||||||
|
} else {
|
||||||
|
button.classList.add('btn-outline-primary');
|
||||||
|
button.classList.remove('btn-primary');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const progress = totalHal > 0 ? Math.round(((currentIndex + 1) / totalHal) * 100) : 100;
|
||||||
|
if (badgeHal) {
|
||||||
|
badgeHal.textContent = 'Halaman ' + currentHal;
|
||||||
|
}
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.style.width = progress + '%';
|
||||||
|
progressBar.setAttribute('aria-valuenow', progress);
|
||||||
|
}
|
||||||
|
if (progressText) {
|
||||||
|
progressText.textContent = progress + '%';
|
||||||
|
}
|
||||||
|
if (summaryHal) {
|
||||||
|
summaryHal.textContent = 'Halaman ' + (currentIndex + 1) + ' dari ' + totalHal;
|
||||||
|
}
|
||||||
|
if (finalSubmitButton) {
|
||||||
|
finalSubmitButton.style.display = isLast ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupLainnyaInputs() {
|
||||||
|
document.querySelectorAll('[data-lainnya-radio]').forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function (event) {
|
||||||
|
const detailId = event.target.getAttribute('data-lainnya-radio');
|
||||||
|
if (!detailId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetWrapper = document.querySelector('[data-lainnya-wrapper="' + detailId + '"]');
|
||||||
|
if (targetWrapper) {
|
||||||
|
handleLainnyaInput(targetWrapper, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('input[type="radio"]').forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function (event) {
|
||||||
|
const detailId = event.target.name.replace('jawaban[', '').replace(']', '');
|
||||||
|
const targetWrapper = document.querySelector('[data-lainnya-wrapper="' + detailId + '"]');
|
||||||
|
|
||||||
|
if (targetWrapper && event.target.value !== 'Lainnya') {
|
||||||
|
handleLainnyaInput(targetWrapper, false, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLainnyaInput(wrapper, show, clearValue = false) {
|
||||||
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapper.classList.toggle('show', show);
|
||||||
|
const input = wrapper.querySelector('input');
|
||||||
|
if (input) {
|
||||||
|
input.required = show;
|
||||||
|
if (show) {
|
||||||
|
input.focus();
|
||||||
|
} else if (clearValue) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
59
resources/views/soal/list.blade.php
Normal file
59
resources/views/soal/list.blade.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@extends('layouts.template')
|
||||||
|
|
||||||
|
@section('title', 'Daftar Kuesioner')
|
||||||
|
|
||||||
|
@section('custom_css')
|
||||||
|
<style>
|
||||||
|
.soal-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soal-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-start {
|
||||||
|
border-radius: 999px;
|
||||||
|
padding-inline: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="py-5">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2 class="fw-bold mb-2">Selamat Datang di Survei Mutu</h2>
|
||||||
|
<p class="text-muted mb-0">Silakan pilih kuesioner yang ingin Anda isi. Pastikan data yang diberikan sesuai kondisi unit kerja Anda.</p>
|
||||||
|
</div>
|
||||||
|
@if (session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="row g-4">
|
||||||
|
@foreach ($daftarSoal as $soal)
|
||||||
|
<div class="col-lg-4 col-md-6">
|
||||||
|
<div class="card soal-card h-100 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||||
|
<span class="badge bg-label-primary">Kuesioner</span>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-semibold mb-2">{{ $soal->judul_soal ?? 'Tanpa Judul' }}</h5>
|
||||||
|
<p class="text-muted flex-grow-1">{{ \Illuminate\Support\Str::limit(strip_tags($soal->keterangan_soal ?? 'Belum ada keterangan'), 120) }}</p>
|
||||||
|
<div class="mt-3 d-flex justify-content-between align-items-center">
|
||||||
|
<a href="{{ route('soal.index', ['start' => 1, 'soal_id' => $soal->id]) }}" class="btn btn-primary btn-sm btn-start">
|
||||||
|
Mulai Isi
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@ -1,9 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AuthController;
|
|
||||||
use App\Http\Controllers\SoalController;
|
use App\Http\Controllers\SoalController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// Route::get('/login', [AuthController::class, 'login']);
|
Route::get('/', [SoalController::class, 'index'])->name('soal.index');
|
||||||
|
Route::post('/jawaban', [SoalController::class, 'store'])->name('soal.store');
|
||||||
Route::get('/', [SoalController::class, 'index']);
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user