progress penggaris
This commit is contained in:
parent
915d3d8f8a
commit
7a44867011
@ -92,7 +92,7 @@ class SoalController extends Controller
|
|||||||
$formLocked = true;
|
$formLocked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$prefillJawaban = $this->generatePrefillJawaban($detailSoal, $pegawai);
|
$prefillJawaban = [];
|
||||||
return view('soal.index', [
|
return view('soal.index', [
|
||||||
'soal' => $soal,
|
'soal' => $soal,
|
||||||
'hal' => $hal,
|
'hal' => $hal,
|
||||||
@ -100,7 +100,7 @@ class SoalController extends Controller
|
|||||||
'daftarHal' => $daftarHal,
|
'daftarHal' => $daftarHal,
|
||||||
'totalHal' => $daftarHal->count(),
|
'totalHal' => $daftarHal->count(),
|
||||||
'soalId' => $soal->id,
|
'soalId' => $soal->id,
|
||||||
'pegawai' => $pegawai,
|
'pegawai' => [],
|
||||||
'prefillJawaban' => $prefillJawaban,
|
'prefillJawaban' => $prefillJawaban,
|
||||||
'existingJawaban' => $existingJawaban,
|
'existingJawaban' => $existingJawaban,
|
||||||
'formLocked' => $formLocked,
|
'formLocked' => $formLocked,
|
||||||
@ -112,8 +112,8 @@ class SoalController extends Controller
|
|||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'lms_mutu_soal_id' => 'required|integer',
|
'lms_mutu_soal_id' => 'required|integer',
|
||||||
'hal' => 'nullable|integer|min:1',
|
'hal' => 'nullable|integer|min:1',
|
||||||
'jawaban' => 'required|array',
|
'jawaban' => 'nullable|array',
|
||||||
'jawaban.*' => 'required',
|
'jawaban.*' => 'nullable',
|
||||||
'jawaban_lainnya' => 'nullable|array',
|
'jawaban_lainnya' => 'nullable|array',
|
||||||
'jawaban_lainnya.*' => 'nullable|string',
|
'jawaban_lainnya.*' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
@ -133,8 +133,9 @@ class SoalController extends Controller
|
|||||||
});
|
});
|
||||||
|
|
||||||
$validated = $validator->validate();
|
$validated = $validator->validate();
|
||||||
|
$jawabanUtama = $validated['jawaban'] ?? [];
|
||||||
|
|
||||||
$detailIds = array_map('intval', array_keys($validated['jawaban']));
|
$detailIds = array_map('intval', array_keys($jawabanUtama));
|
||||||
$detailMeta = SoalDetail::whereIn('id', $detailIds)
|
$detailMeta = SoalDetail::whereIn('id', $detailIds)
|
||||||
->get(['id', 'soal'])
|
->get(['id', 'soal'])
|
||||||
->keyBy('id');
|
->keyBy('id');
|
||||||
@ -144,16 +145,16 @@ class SoalController extends Controller
|
|||||||
$namaResponden = null;
|
$namaResponden = null;
|
||||||
$unitKerja = null;
|
$unitKerja = null;
|
||||||
$pegawai = session('pegawai') ?? [];
|
$pegawai = session('pegawai') ?? [];
|
||||||
$pegawaiId = is_array($pegawai) ? ($pegawai['id'] ?? null) : null;
|
$pegawaiId = is_array($pegawai) ? ($pegawai['id'] ?? null) : 1;
|
||||||
|
|
||||||
DB::connection('dbLmsMutu')->transaction(function () use (&$jawabanBaru, $validated, $jawabanLainnya, $detailMeta, &$namaResponden, &$unitKerja, $pegawaiId) {
|
DB::connection('dbLmsMutu')->transaction(function () use (&$jawabanBaru, $validated, $jawabanUtama, $jawabanLainnya, $detailMeta, &$namaResponden, &$unitKerja, $pegawaiId) {
|
||||||
$jawabanBaru = Jawaban::create([
|
$jawabanBaru = Jawaban::create([
|
||||||
'lms_mutu_soal_id' => $validated['lms_mutu_soal_id'],
|
'lms_mutu_soal_id' => $validated['lms_mutu_soal_id'],
|
||||||
'pegawai_id' => $pegawaiId,
|
'pegawai_id' => $pegawaiId,
|
||||||
'tanggal_isi' => Carbon::now()->addHour(7),
|
'tanggal_isi' => Carbon::now()->addHour(7),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($validated['jawaban'] as $detailId => $answer) {
|
foreach ($jawabanUtama as $detailId => $answer) {
|
||||||
if (is_array($answer)) {
|
if (is_array($answer)) {
|
||||||
$answer = json_encode($answer);
|
$answer = json_encode($answer);
|
||||||
}
|
}
|
||||||
@ -205,9 +206,15 @@ class SoalController extends Controller
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect('/')
|
return redirect()->route('soal.thankyou')
|
||||||
->with('success', 'Jawaban berhasil disimpan.');
|
->with('success', 'Jawaban berhasil disimpan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function thankYou()
|
||||||
|
{
|
||||||
|
return view('soal.thank-you');
|
||||||
|
}
|
||||||
|
|
||||||
public function redirectSmart()
|
public function redirectSmart()
|
||||||
{
|
{
|
||||||
$data = request()->input('data');
|
$data = request()->input('data');
|
||||||
|
|||||||
@ -3,88 +3,165 @@
|
|||||||
@section('title', 'Kuesioner Soal')
|
@section('title', 'Kuesioner Soal')
|
||||||
|
|
||||||
@section('custom_css')
|
@section('custom_css')
|
||||||
<link rel="stylesheet" href="{{ asset('vuexy/assets/vendor/libs/select2/select2.css') }}">
|
|
||||||
<style>
|
<style>
|
||||||
.question-card {
|
.question-card {
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-layout .question-text-col {
|
|
||||||
border-right: 1px solid #f1f3f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-layout .answer-section {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.question-layout .question-text-col {
|
.question-layout .question-text-col {
|
||||||
border-right: 0;
|
border-right: 1px solid #f1f3f5;
|
||||||
border-bottom: 1px dashed #e9ecef;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-layout .answer-section {
|
.question-layout .answer-section {
|
||||||
padding-left: 0;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.option-scroll {
|
@media (max-width: 767.98px) {
|
||||||
max-height: 220px;
|
.question-layout .question-text-col {
|
||||||
overflow-y: auto;
|
border-right: 0;
|
||||||
border: 1px dashed #ced4da;
|
border-bottom: 1px dashed #e9ecef;
|
||||||
border-radius: 0.65rem;
|
padding-bottom: 1rem;
|
||||||
padding: 0.75rem 1rem;
|
margin-bottom: 1rem;
|
||||||
background-color: #f8f9fa;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container {
|
.question-layout .answer-section {
|
||||||
width: 100% !important;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.select2-container .select2-selection--single {
|
.option-scroll {
|
||||||
min-height: calc(2.625rem + 2px);
|
max-height: 220px;
|
||||||
padding: 0.5rem 1rem;
|
overflow-y: auto;
|
||||||
border: 1px solid #ced4da;
|
border: 1px dashed #ced4da;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.65rem;
|
||||||
}
|
padding: 0.75rem 1rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
.option-scroll::-webkit-scrollbar {
|
||||||
line-height: 1.6;
|
width: 6px;
|
||||||
color: #4f4f4f;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
.option-scroll::-webkit-scrollbar-thumb {
|
||||||
top: 0.6rem;
|
background-color: rgba(13, 110, 253, 0.4);
|
||||||
right: 1rem;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-scroll::-webkit-scrollbar {
|
.option-search {
|
||||||
width: 6px;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-scroll::-webkit-scrollbar-thumb {
|
.option-search .search-icon {
|
||||||
background-color: rgba(13, 110, 253, 0.4);
|
position: absolute;
|
||||||
border-radius: 3px;
|
top: 50%;
|
||||||
}
|
left: 0.85rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #adb5bd;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.lainnya-input {
|
.option-search .search-icon svg {
|
||||||
display: none;
|
width: 14px;
|
||||||
}
|
height: 14px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.lainnya-input.show {
|
.option-search input {
|
||||||
display: block;
|
padding-left: 2.4rem;
|
||||||
margin-top: 0.85rem;
|
}
|
||||||
}
|
|
||||||
|
.lainnya-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lainnya-input.show {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-form-wrapper .form-label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
/* Skala di atas slider */
|
||||||
|
.range-scale {
|
||||||
|
position: relative;
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-track-colored .range-colored {
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: #ffffff; /* default putih, nanti diwarnai JS */
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hilangkan track default browser */
|
||||||
|
.range-colored::-webkit-slider-runnable-track {
|
||||||
|
background: transparent;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
.range-colored::-moz-range-track {
|
||||||
|
background: transparent;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* thumb */
|
||||||
|
.range-colored::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #000;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.range-colored::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #000;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.range-scale-item {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-scale-mark {
|
||||||
|
display: block;
|
||||||
|
width: 1px;
|
||||||
|
height: 8px;
|
||||||
|
margin-top:-10px;
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tick besar tiap 10 */
|
||||||
|
.range-scale-item-major .range-scale-mark {
|
||||||
|
height: 12px;
|
||||||
|
background-color: #000;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tick khusus angka 1 biar lebih pendek (opsional) */
|
||||||
|
.range-scale-item-one .range-scale-mark {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.dual-form-wrapper .form-label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@ -170,12 +247,23 @@
|
|||||||
<input type="hidden" name="lms_mutu_soal_id" value="{{ $soal->id }}">
|
<input type="hidden" name="lms_mutu_soal_id" value="{{ $soal->id }}">
|
||||||
<input type="hidden" name="hal" id="input-hal" value="{{ $hal }}">
|
<input type="hidden" name="hal" id="input-hal" value="{{ $hal }}">
|
||||||
|
|
||||||
|
@php
|
||||||
|
$questionCounter = 0;
|
||||||
|
@endphp
|
||||||
@forelse ($soal->soalDetail as $detail)
|
@forelse ($soal->soalDetail as $detail)
|
||||||
@php
|
@php
|
||||||
$detailConfig = json_decode($detail->soal, true) ?? [];
|
$detailConfig = json_decode($detail->soal, true) ?? [];
|
||||||
$pertanyaan = $detailConfig['soal'] ?? 'Pertanyaan tidak tersedia';
|
$pertanyaan = $detailConfig['soal'] ?? 'Pertanyaan tidak tersedia';
|
||||||
$type = $detailConfig['type'] ?? 'option';
|
$type = $detailConfig['type'] ?? 'option';
|
||||||
$options = $detailConfig['options'] ?? [];
|
$optionsRaw = $detailConfig['options'] ?? [];
|
||||||
|
if (is_array($optionsRaw)) {
|
||||||
|
$options = $optionsRaw;
|
||||||
|
} elseif ($type === 'option_with_range' && is_string($optionsRaw)) {
|
||||||
|
$options = [$optionsRaw];
|
||||||
|
} else {
|
||||||
|
$options = [];
|
||||||
|
}
|
||||||
|
$autoSelect = ($type === 'option' && count($options) == 1) ? true : false;
|
||||||
if (!is_array($options)) {
|
if (!is_array($options)) {
|
||||||
$options = [];
|
$options = [];
|
||||||
}
|
}
|
||||||
@ -184,7 +272,12 @@
|
|||||||
})->all();
|
})->all();
|
||||||
$existingAnswer = $existingJawaban[$detail->id] ?? null;
|
$existingAnswer = $existingJawaban[$detail->id] ?? null;
|
||||||
$prefillAnswer = $prefillJawaban[$detail->id] ?? null;
|
$prefillAnswer = $prefillJawaban[$detail->id] ?? null;
|
||||||
|
$hasDefaultAnswer = array_key_exists('default', $detailConfig);
|
||||||
|
$defaultAnswer = $hasDefaultAnswer ? $detailConfig['default'] : null;
|
||||||
$currentAnswer = old('jawaban.' . $detail->id, $existingAnswer ?? $prefillAnswer);
|
$currentAnswer = old('jawaban.' . $detail->id, $existingAnswer ?? $prefillAnswer);
|
||||||
|
if (($currentAnswer === null || $currentAnswer === '') && $hasDefaultAnswer) {
|
||||||
|
$currentAnswer = $defaultAnswer;
|
||||||
|
}
|
||||||
$existingOtherAnswer = $existingAnswer && !in_array($existingAnswer, $optionValues, true) ? $existingAnswer : null;
|
$existingOtherAnswer = $existingAnswer && !in_array($existingAnswer, $optionValues, true) ? $existingAnswer : null;
|
||||||
$prefillOtherAnswer = $prefillAnswer && !in_array($prefillAnswer, $optionValues, true) ? $prefillAnswer : null;
|
$prefillOtherAnswer = $prefillAnswer && !in_array($prefillAnswer, $optionValues, true) ? $prefillAnswer : null;
|
||||||
$oldOtherAnswer = old('jawaban_lainnya.' . $detail->id, $existingOtherAnswer ?? $prefillOtherAnswer);
|
$oldOtherAnswer = old('jawaban_lainnya.' . $detail->id, $existingOtherAnswer ?? $prefillOtherAnswer);
|
||||||
@ -199,11 +292,12 @@
|
|||||||
$detailHal = $detail->hal ?? $listHal->first();
|
$detailHal = $detail->hal ?? $listHal->first();
|
||||||
$isVisible = $detailHal == $hal;
|
$isVisible = $detailHal == $hal;
|
||||||
$optionsCount = is_array($options) ? count($options) : 0;
|
$optionsCount = is_array($options) ? count($options) : 0;
|
||||||
$useSelectSearch = $optionsCount > 4;
|
|
||||||
$hasLainnyaOption = collect($options)->contains(function ($optionItem) {
|
$hasLainnyaOption = collect($options)->contains(function ($optionItem) {
|
||||||
return is_string($optionItem) && stripos($optionItem, 'lainnya') !== false;
|
return is_string($optionItem) && stripos($optionItem, 'lainnya') !== false;
|
||||||
});
|
});
|
||||||
if (!$hasLainnyaOption && $type === 'option_with_other') {
|
if (!$hasLainnyaOption && $type === 'option_with_other') {
|
||||||
|
$options[] = 'Lainnya';
|
||||||
|
$optionsCount = count($options);
|
||||||
$hasLainnyaOption = true;
|
$hasLainnyaOption = true;
|
||||||
}
|
}
|
||||||
$isConsentQuestion = !empty($detailConfig['persetujuan_form']);
|
$isConsentQuestion = !empty($detailConfig['persetujuan_form']);
|
||||||
@ -212,6 +306,12 @@
|
|||||||
$useDualForm = $type === 'dual_form' || (!empty($dualFormConfig) && $dualFormConfig !== false);
|
$useDualForm = $type === 'dual_form' || (!empty($dualFormConfig) && $dualFormConfig !== false);
|
||||||
$dualYearOld = null;
|
$dualYearOld = null;
|
||||||
$dualMonthOld = null;
|
$dualMonthOld = null;
|
||||||
|
$questionNumber = $detailConfig['no'] ?? '';
|
||||||
|
$rangeMin = null;
|
||||||
|
$rangeMax = null;
|
||||||
|
$rangeStep = 1;
|
||||||
|
$rangeDefault = null;
|
||||||
|
$rangeTicks = [];
|
||||||
if ($useDualForm && $currentAnswer) {
|
if ($useDualForm && $currentAnswer) {
|
||||||
if (preg_match('/([0-9]+)\\s*\\(Tahun\\)/i', $currentAnswer, $matchYear)) {
|
if (preg_match('/([0-9]+)\\s*\\(Tahun\\)/i', $currentAnswer, $matchYear)) {
|
||||||
$dualYearOld = $matchYear[1];
|
$dualYearOld = $matchYear[1];
|
||||||
@ -220,8 +320,48 @@
|
|||||||
$dualMonthOld = $matchMonth[1];
|
$dualMonthOld = $matchMonth[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@endphp
|
if ($type === 'option_with_range' && !empty($options)) {
|
||||||
|
$rangeSource = $options[0];
|
||||||
|
if (is_array($rangeSource)) {
|
||||||
|
$rangeSource = $rangeSource['range'] ?? (isset($rangeSource['min'], $rangeSource['max']) ? $rangeSource['min'] . '-' . $rangeSource['max'] : null);
|
||||||
|
}
|
||||||
|
if (is_string($rangeSource) && preg_match('/(-?[0-9]+(?:[\\.,][0-9]+)?)\\s*-\\s*(-?[0-9]+(?:[\\.,][0-9]+)?)/', $rangeSource, $rangeMatch)) {
|
||||||
|
$rangeMin = (float) str_replace(',', '.', $rangeMatch[1]);
|
||||||
|
$rangeMax = (float) str_replace(',', '.', $rangeMatch[2]);
|
||||||
|
if ($rangeMin > $rangeMax) {
|
||||||
|
[$rangeMin, $rangeMax] = [$rangeMax, $rangeMin];
|
||||||
|
}
|
||||||
|
$rangeUsesDecimal = (fmod($rangeMin, 1) !== 0.0) || (fmod($rangeMax, 1) !== 0.0);
|
||||||
|
$rangeStep = $rangeUsesDecimal ? 0.1 : 1;
|
||||||
|
if (is_numeric($currentAnswer)) {
|
||||||
|
$numericAnswer = (float) $currentAnswer;
|
||||||
|
$rangeDefault = min(max($numericAnswer, $rangeMin), $rangeMax);
|
||||||
|
} else {
|
||||||
|
$rangeDefault = $rangeMin;
|
||||||
|
}
|
||||||
|
if ($rangeDefault === null) {
|
||||||
|
$rangeDefault = $rangeMin;
|
||||||
|
}
|
||||||
|
$rangeSpan = max($rangeMax - $rangeMin, 1);
|
||||||
|
|
||||||
|
$startTick = (int) ceil($rangeMin);
|
||||||
|
$endTick = (int) floor($rangeMax);
|
||||||
|
|
||||||
|
for ($v = $startTick; $v <= $endTick; $v++) {
|
||||||
|
$position = (($v - $rangeMin) / $rangeSpan) * 100;
|
||||||
|
|
||||||
|
$rangeTicks[] = [
|
||||||
|
'value' => $v, // nilai asli
|
||||||
|
'label' => ($v === 1 || $v % 10 === 0) ? $v : null, // label hanya 1 & kelipatan 10
|
||||||
|
'position' => max(0, min(100, $position)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rangeMinDisplay = ($rangeMin !== null && floor($rangeMin) == $rangeMin) ? (int) $rangeMin : $rangeMin;
|
||||||
|
$rangeMaxDisplay = ($rangeMax !== null && floor($rangeMax) == $rangeMax) ? (int) $rangeMax : $rangeMax;
|
||||||
|
$rangeDefaultDisplay = ($rangeDefault !== null && floor($rangeDefault) == $rangeDefault) ? (int) $rangeDefault : $rangeDefault;
|
||||||
|
@endphp
|
||||||
<div class="question-card mb-4"
|
<div class="question-card mb-4"
|
||||||
data-hal-card="{{ $detailHal }}"
|
data-hal-card="{{ $detailHal }}"
|
||||||
data-detail-id="{{ $detail->id }}"
|
data-detail-id="{{ $detail->id }}"
|
||||||
@ -231,7 +371,9 @@
|
|||||||
<div class="row g-3 align-items-start question-layout">
|
<div class="row g-3 align-items-start question-layout">
|
||||||
<div class="col-md-6 question-text-col">
|
<div class="col-md-6 question-text-col">
|
||||||
<div class="d-flex align-items-center gap-3 mb-1">
|
<div class="d-flex align-items-center gap-3 mb-1">
|
||||||
<span class="badge rounded-pill bg-label-primary fs-6">{{ $loop->iteration }}</span>
|
@if ($questionNumber !== null)
|
||||||
|
<span class="badge rounded-pill bg-label-primary fs-6">{{ $questionNumber }}</span>
|
||||||
|
@endif
|
||||||
<h5 class="fw-semibold mb-0">{{ $pertanyaan }}</h5>
|
<h5 class="fw-semibold mb-0">{{ $pertanyaan }}</h5>
|
||||||
</div>
|
</div>
|
||||||
@if ($isConsentQuestion)
|
@if ($isConsentQuestion)
|
||||||
@ -269,134 +411,169 @@
|
|||||||
data-dual-hidden="{{ $detail->id }}"
|
data-dual-hidden="{{ $detail->id }}"
|
||||||
data-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif>
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
@elseif ($type === 'textarea')
|
@elseif ($type === 'textarea')
|
||||||
<textarea class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
<textarea class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
name="jawaban[{{ $detail->id }}]" rows="4" required
|
name="jawaban[{{ $detail->id }}]" rows="4"
|
||||||
data-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif
|
||||||
placeholder="Tulis jawaban Anda di sini">{{ $currentAnswer }}</textarea>
|
placeholder="Tulis jawaban Anda di sini">{{ $currentAnswer }}</textarea>
|
||||||
@elseif ($type === 'text')
|
@elseif ($type === 'text')
|
||||||
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
name="jawaban[{{ $detail->id }}]" value="{{ $currentAnswer }}" required
|
name="jawaban[{{ $detail->id }}]" value="{{ $currentAnswer }}"
|
||||||
data-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif
|
||||||
placeholder="Masukkan jawaban Anda">
|
placeholder="Masukkan jawaban Anda">
|
||||||
|
@elseif ($type === 'option_with_range')
|
||||||
|
@if ($rangeMin !== null && $rangeMax !== null)
|
||||||
|
<div class="range-slider-wrapper" data-range-wrapper="{{ $detail->id }}">
|
||||||
|
<div class="range-scale" aria-hidden="true">
|
||||||
|
@foreach ($rangeTicks as $tick)
|
||||||
|
@php
|
||||||
|
$value = (int) $tick['value'];
|
||||||
|
$label = $tick['label']; // bisa null
|
||||||
|
$isMajor = ($value === 1) || ($value % 10 === 0);
|
||||||
|
$isOneTick = ($value === 1);
|
||||||
|
@endphp
|
||||||
|
<div class="range-scale-item
|
||||||
|
{{ $isMajor ? 'range-scale-item-major' : '' }}
|
||||||
|
{{ $isOneTick ? 'range-scale-item-one' : '' }}"
|
||||||
|
style="left: {{ $tick['position'] }}%;">
|
||||||
|
<span class="range-scale-mark"></span>
|
||||||
|
|
||||||
|
@if(!is_null($label))
|
||||||
|
<span class="range-scale-label">{{ $label }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{-- Track warna + slider --}}
|
||||||
|
<div class="range-track-colored">
|
||||||
|
<input type="range"
|
||||||
|
class="form-range range-colored border"
|
||||||
|
name="jawaban[{{ $detail->id }}]"
|
||||||
|
min="{{ $rangeMin }}"
|
||||||
|
max="{{ $rangeMax }}"
|
||||||
|
step="{{ $rangeStep }}"
|
||||||
|
value="{{ $rangeDefault ?? $rangeMin }}"
|
||||||
|
data-range-input="{{ $detail->id }}"
|
||||||
|
data-range-min-value="{{ $rangeMin }}"
|
||||||
|
data-range-max-value="{{ $rangeMax }}"
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
@if($formLocked) disabled @endif>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-2 fw-semibold">
|
||||||
|
Nilai saat ini:
|
||||||
|
<span data-range-output="{{ $detail->id }}">{{ $rangeDefaultDisplay ?? $rangeMinDisplay }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm mt-3 range-manual-input">
|
||||||
|
<span class="input-group-text">Atau isi manual</span>
|
||||||
|
<input type="number"
|
||||||
|
class="form-control"
|
||||||
|
min="{{ $rangeMin }}"
|
||||||
|
max="{{ $rangeMax }}"
|
||||||
|
step="{{ $rangeStep }}"
|
||||||
|
value="{{ $rangeDefault ?? $rangeMin }}"
|
||||||
|
data-range-manual="{{ $detail->id }}"
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
|
@if($formLocked) disabled @endif>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
name="jawaban[{{ $detail->id }}]" value="{{ $currentAnswer }}"
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
|
@if($formLocked) disabled @endif
|
||||||
|
placeholder="Masukkan jawaban Anda">
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
@if (!empty($options))
|
@if (!empty($options))
|
||||||
@if ($useSelectSearch)
|
@php
|
||||||
<select class="form-select select2 @error('jawaban.' . $detail->id) is-invalid @enderror"
|
$lainnyaWrapperRendered = false;
|
||||||
name="jawaban[{{ $detail->id }}]"
|
@endphp
|
||||||
data-field-hal="{{ $detailHal }}"
|
<div class="option-search mb-3">
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if(count($options) > 6)
|
||||||
@if ($hasLainnyaOption || $type === 'option_with_other') data-lainnya-select="{{ $detail->id }}" @endif
|
<input type="text"
|
||||||
data-select-search="true"
|
class="form-control form-control-sm option-search-input"
|
||||||
@if($formLocked) disabled @endif
|
placeholder="Cari jawaban"
|
||||||
required>
|
data-option-search="{{ $detail->id }}"
|
||||||
<option value="" disabled {{ $currentAnswer ? '' : 'selected' }}>Pilih jawaban</option>
|
autocomplete="off">
|
||||||
@foreach ($options as $option)
|
@endif
|
||||||
@php
|
</div>
|
||||||
$optionLabel = is_scalar($option) ? (string) $option : '';
|
<div class="option-scroll" data-option-list="{{ $detail->id }}">
|
||||||
$isLainnya = stripos($optionLabel, 'lainnya') !== false;
|
@foreach ($options as $optionIndex => $option)
|
||||||
$optionValue = $isLainnya && $oldOtherAnswer ? $oldOtherAnswer : $optionLabel;
|
@php
|
||||||
$shouldSelect = $currentAnswer === $optionValue || ($isLainnya && $shouldForceLainnyaSelection);
|
$optionId = 'jawaban-' . $detail->id . '-' . $optionIndex;
|
||||||
@endphp
|
$optionLabel = is_scalar($option) ? (string) $option : '';
|
||||||
<option value="{{ $optionValue }}"
|
$isLainnya = stripos($optionLabel, 'lainnya') !== false;
|
||||||
|
$optionValue = $isLainnya && $oldOtherAnswer ? $oldOtherAnswer : $optionLabel;
|
||||||
|
$shouldCheck = $currentAnswer === $optionValue || ($isLainnya && $shouldForceLainnyaSelection);
|
||||||
|
@endphp
|
||||||
|
<div class="form-check mb-2" data-option-item>
|
||||||
|
<input class="form-check-input @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
|
type="radio"
|
||||||
|
name="jawaban[{{ $detail->id }}]"
|
||||||
|
id="{{ $optionId }}"
|
||||||
|
value="{{ $optionValue }}"
|
||||||
data-original-value="{{ $optionLabel }}"
|
data-original-value="{{ $optionLabel }}"
|
||||||
@if ($isLainnya) data-lainnya-option="{{ $detail->id }}" @endif
|
data-lainnya-radio="{{ $isLainnya ? $detail->id : '' }}"
|
||||||
{{ $shouldSelect ? 'selected' : '' }}>
|
|
||||||
{{ $optionLabel }}
|
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
@if ($type === 'option_with_other' && !$hasLainnyaOption)
|
|
||||||
<option value="Lainnya"
|
|
||||||
data-original-value="Lainnya"
|
|
||||||
data-lainnya-option="{{ $detail->id }}">
|
|
||||||
Lainnya
|
|
||||||
</option>
|
|
||||||
@endif
|
|
||||||
</select>
|
|
||||||
@if ($hasLainnyaOption || $type === 'option_with_other')
|
|
||||||
<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-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
data-lainnya-input="{{ $detail->id }}"
|
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif
|
||||||
placeholder="Tuliskan jawaban lainnya"
|
{{ $shouldCheck || $autoSelect ? 'checked' : '' }}>
|
||||||
{{ $showLainnya ? 'required' : '' }}>
|
<label class="form-check-label" for="{{ $optionId }}">
|
||||||
|
{{ $optionLabel }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
@else
|
@if ($isLainnya)
|
||||||
@php
|
|
||||||
$lainnyaWrapperRendered = false;
|
|
||||||
@endphp
|
|
||||||
<div class="option-scroll">
|
|
||||||
@foreach ($options as $optionIndex => $option)
|
|
||||||
@php
|
@php
|
||||||
$optionId = 'jawaban-' . $detail->id . '-' . $optionIndex;
|
$lainnyaWrapperRendered = true;
|
||||||
$optionLabel = is_scalar($option) ? (string) $option : '';
|
|
||||||
$isLainnya = stripos($optionLabel, 'lainnya') !== false;
|
|
||||||
$optionValue = $isLainnya && $oldOtherAnswer ? $oldOtherAnswer : $optionLabel;
|
|
||||||
$shouldCheck = $currentAnswer === $optionValue || ($isLainnya && $shouldForceLainnyaSelection);
|
|
||||||
@endphp
|
@endphp
|
||||||
<div class="form-check mb-2">
|
<div class="lainnya-input {{ $showLainnya ? 'show' : '' }}" data-lainnya-wrapper="{{ $detail->id }}">
|
||||||
<input class="form-check-input @error('jawaban.' . $detail->id) is-invalid @enderror"
|
<input type="text" class="form-control form-control-sm mt-2"
|
||||||
type="radio"
|
name="jawaban_lainnya[{{ $detail->id }}]"
|
||||||
name="jawaban[{{ $detail->id }}]"
|
value="{{ $oldOtherAnswer }}"
|
||||||
id="{{ $optionId }}"
|
data-lainnya-input="{{ $detail->id }}"
|
||||||
value="{{ $optionValue }}"
|
|
||||||
data-original-value="{{ $optionLabel }}"
|
|
||||||
data-lainnya-radio="{{ $isLainnya ? $detail->id : '' }}" required
|
|
||||||
data-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif
|
||||||
{{ $shouldCheck ? 'checked' : '' }}>
|
placeholder="Tuliskan jawaban lainnya">
|
||||||
<label class="form-check-label" for="{{ $optionId }}">
|
|
||||||
{{ $optionLabel }}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
@if ($isLainnya)
|
@endif
|
||||||
@php
|
@endforeach
|
||||||
$lainnyaWrapperRendered = true;
|
</div>
|
||||||
@endphp
|
@if ($hasLainnyaOption || $type === 'option_with_other')
|
||||||
<div class="lainnya-input {{ $showLainnya ? 'show' : '' }}" data-lainnya-wrapper="{{ $detail->id }}">
|
<small class="form-text text-muted">
|
||||||
<input type="text" class="form-control form-control-sm mt-2"
|
Jika jawaban yang Anda cari tidak ada di daftar, pilih opsi <strong>"Lainnya"</strong> lalu isi sesuai kebutuhan.
|
||||||
name="jawaban_lainnya[{{ $detail->id }}]"
|
</small>
|
||||||
value="{{ $oldOtherAnswer }}"
|
@endif
|
||||||
data-lainnya-input="{{ $detail->id }}"
|
<div class="text-muted small mt-2" data-option-empty="{{ $detail->id }}" style="display: none;">
|
||||||
data-field-hal="{{ $detailHal }}"
|
Tidak ada jawaban yang cocok.
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
</div>
|
||||||
@if($formLocked) disabled @endif
|
@if (!$lainnyaWrapperRendered && $type === 'option_with_other')
|
||||||
placeholder="Tuliskan jawaban lainnya"
|
<div class="lainnya-input {{ $showLainnya ? 'show' : '' }}" data-lainnya-wrapper="{{ $detail->id }}">
|
||||||
{{ $showLainnya ? 'required' : '' }}>
|
<input type="text" class="form-control form-control-sm mt-2"
|
||||||
</div>
|
name="jawaban_lainnya[{{ $detail->id }}]"
|
||||||
@endif
|
value="{{ $oldOtherAnswer }}"
|
||||||
@endforeach
|
data-lainnya-input="{{ $detail->id }}"
|
||||||
|
data-field-hal="{{ $detailHal }}"
|
||||||
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
|
@if($formLocked) disabled @endif
|
||||||
|
placeholder="Tuliskan jawaban lainnya">
|
||||||
</div>
|
</div>
|
||||||
@if (!$lainnyaWrapperRendered && $type === 'option_with_other')
|
|
||||||
<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 }}"
|
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
|
||||||
@if($formLocked) disabled @endif
|
|
||||||
placeholder="Tuliskan jawaban lainnya"
|
|
||||||
{{ $showLainnya ? 'required' : '' }}>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
<input type="text" class="form-control @error('jawaban.' . $detail->id) is-invalid @enderror"
|
||||||
name="jawaban[{{ $detail->id }}]" value="{{ $currentAnswer }}" required
|
name="jawaban[{{ $detail->id }}]" value="{{ $currentAnswer }}"
|
||||||
data-field-hal="{{ $detailHal }}"
|
data-field-hal="{{ $detailHal }}"
|
||||||
@if ($isConsentQuestion) data-consent-input="1" @endif
|
@if ($isConsentQuestion) data-consent-input="1" @endif
|
||||||
@if($formLocked) disabled @endif
|
@if($formLocked) disabled @endif
|
||||||
@ -455,7 +632,6 @@
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('custom_js')
|
@section('custom_js')
|
||||||
<script src="{{ asset('vuexy/assets/vendor/libs/select2/select2.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const form = document.getElementById('form-soal');
|
const form = document.getElementById('form-soal');
|
||||||
@ -533,8 +709,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
initSelectSearch();
|
|
||||||
setupLainnyaInputs();
|
setupLainnyaInputs();
|
||||||
|
setupOptionSearch();
|
||||||
|
setupRangeInputs();
|
||||||
setupDualFormInputs();
|
setupDualFormInputs();
|
||||||
setupConsentWatcher();
|
setupConsentWatcher();
|
||||||
updateQuestionVisibility();
|
updateQuestionVisibility();
|
||||||
@ -693,11 +870,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupLainnyaInputs() {
|
function setupLainnyaInputs() {
|
||||||
document.querySelectorAll('input[type="radio"]').forEach(function (radio) {
|
const radios = form.querySelectorAll('input[type="radio"][name^="jawaban["]');
|
||||||
|
radios.forEach(function (radio) {
|
||||||
radio.addEventListener('change', function (event) {
|
radio.addEventListener('change', function (event) {
|
||||||
const customDetailId = event.target.getAttribute('data-lainnya-radio');
|
const customDetailId = event.target.getAttribute('data-lainnya-radio');
|
||||||
const detailId = customDetailId || event.target.name.replace('jawaban[', '').replace(']', '');
|
const detailId = customDetailId || event.target.name.replace('jawaban[', '').replace(']', '');
|
||||||
const targetWrapper = detailId ? document.querySelector('[data-lainnya-wrapper="' + detailId + '"]') : null;
|
const targetWrapper = detailId ? form.querySelector('[data-lainnya-wrapper="' + detailId + '"]') : null;
|
||||||
const labelText = getLabelTextByInput(event.target);
|
const labelText = getLabelTextByInput(event.target);
|
||||||
const shouldShow = !!customDetailId
|
const shouldShow = !!customDetailId
|
||||||
|| isOtherChoice(event.target.value)
|
|| isOtherChoice(event.target.value)
|
||||||
@ -714,19 +892,15 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('[data-lainnya-input]').forEach(function (input) {
|
form.querySelectorAll('[data-lainnya-input]').forEach(function (input) {
|
||||||
input.addEventListener('input', function (event) {
|
input.addEventListener('input', function (event) {
|
||||||
const detailId = input.getAttribute('data-lainnya-input');
|
const detailId = input.getAttribute('data-lainnya-input');
|
||||||
syncCustomOptionValue(detailId, event.target.value);
|
syncCustomOptionValue(detailId, event.target.value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('[data-lainnya-select]').forEach(function (select) {
|
form.querySelectorAll('[data-lainnya-wrapper].show').forEach(function (wrapper) {
|
||||||
const detailId = select.name.replace('jawaban[', '').replace(']', '');
|
handleLainnyaInput(wrapper, true);
|
||||||
select.addEventListener('change', function (event) {
|
|
||||||
handleLainnyaSelect(event.target, detailId, true);
|
|
||||||
});
|
|
||||||
handleLainnyaSelect(select, detailId, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,67 +928,160 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLainnyaSelect(selectElement, detailId, canClear) {
|
|
||||||
if (!selectElement || !detailId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const targetWrapper = document.querySelector('[data-lainnya-wrapper="' + detailId + '"]');
|
|
||||||
if (!targetWrapper) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
|
||||||
const optionText = selectedOption ? (selectedOption.textContent || selectedOption.innerText || '') : '';
|
|
||||||
const originalValue = selectedOption ? (selectedOption.dataset.originalValue || '') : '';
|
|
||||||
const isLainnyaSelected = selectedOption && (
|
|
||||||
selectedOption.dataset.lainnyaOption
|
|
||||||
|| isOtherChoice(selectElement.value)
|
|
||||||
|| isOtherChoice(optionText)
|
|
||||||
|| isOtherChoice(originalValue)
|
|
||||||
);
|
|
||||||
if (isLainnyaSelected) {
|
|
||||||
handleLainnyaInput(targetWrapper, true);
|
|
||||||
} else {
|
|
||||||
handleLainnyaInput(targetWrapper, false, canClear);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncCustomOptionValue(detailId, customValue) {
|
function syncCustomOptionValue(detailId, customValue) {
|
||||||
if (!detailId) {
|
if (!detailId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const trimmedValue = (customValue || '').toString().trim();
|
const trimmedValue = (customValue || '').toString().trim();
|
||||||
const radio = document.querySelector('input[data-lainnya-radio="' + detailId + '"]');
|
const radio = form.querySelector('input[data-lainnya-radio="' + detailId + '"]');
|
||||||
if (radio) {
|
if (radio) {
|
||||||
const originalValue = radio.dataset.originalValue || 'lainnya';
|
const originalValue = radio.dataset.originalValue || 'lainnya';
|
||||||
radio.value = trimmedValue || originalValue;
|
radio.value = trimmedValue || originalValue;
|
||||||
}
|
}
|
||||||
const select = document.querySelector('[data-lainnya-select="' + detailId + '"]');
|
|
||||||
if (select) {
|
|
||||||
const option = select.querySelector('[data-lainnya-option="' + detailId + '"]');
|
|
||||||
if (option) {
|
|
||||||
const originalOptionValue = option.dataset.originalValue || 'lainnya';
|
|
||||||
option.value = trimmedValue || originalOptionValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetCustomOptionValue(detailId) {
|
function resetCustomOptionValue(detailId) {
|
||||||
if (!detailId) {
|
if (!detailId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const radio = document.querySelector('input[data-lainnya-radio="' + detailId + '"]');
|
const radio = form.querySelector('input[data-lainnya-radio="' + detailId + '"]');
|
||||||
if (radio && radio.dataset.originalValue) {
|
if (radio && radio.dataset.originalValue) {
|
||||||
radio.value = radio.dataset.originalValue;
|
radio.value = radio.dataset.originalValue;
|
||||||
}
|
}
|
||||||
const select = document.querySelector('[data-lainnya-select="' + detailId + '"]');
|
}
|
||||||
if (select) {
|
|
||||||
const option = select.querySelector('[data-lainnya-option="' + detailId + '"]');
|
function setupOptionSearch() {
|
||||||
if (option && option.dataset.originalValue) {
|
const searchInputs = form.querySelectorAll('[data-option-search]');
|
||||||
option.value = option.dataset.originalValue;
|
searchInputs.forEach(function (input) {
|
||||||
|
const detailId = input.getAttribute('data-option-search');
|
||||||
|
const listContainer = detailId ? form.querySelector('[data-option-list="' + detailId + '"]') : null;
|
||||||
|
if (!listContainer) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const emptyState = detailId ? form.querySelector('[data-option-empty="' + detailId + '"]') : null;
|
||||||
|
const handler = function () {
|
||||||
|
filterOptionList(listContainer, input.value, emptyState);
|
||||||
|
};
|
||||||
|
input.addEventListener('input', handler);
|
||||||
|
handler();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOptionList(container, query, emptyState) {
|
||||||
|
const normalized = (query || '').toString().trim().toLowerCase();
|
||||||
|
let visibleCount = 0;
|
||||||
|
container.querySelectorAll('[data-option-item]').forEach(function (item) {
|
||||||
|
const label = item.querySelector('.form-check-label');
|
||||||
|
const text = (label ? label.textContent : item.textContent || '').toString().trim().toLowerCase();
|
||||||
|
const shouldShow = !normalized || text.indexOf(normalized) !== -1;
|
||||||
|
item.style.display = shouldShow ? '' : 'none';
|
||||||
|
if (shouldShow) {
|
||||||
|
visibleCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (emptyState) {
|
||||||
|
emptyState.style.display = visibleCount ? 'none' : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupRangeInputs() {
|
||||||
|
const rangeInputs = form.querySelectorAll('[data-range-input]');
|
||||||
|
|
||||||
|
rangeInputs.forEach(function (input) {
|
||||||
|
const detailId = input.getAttribute('data-range-input');
|
||||||
|
const output = detailId ? form.querySelector('[data-range-output="' + detailId + '"]') : null;
|
||||||
|
const manualInput = detailId ? form.querySelector('[data-range-manual="' + detailId + '"]') : null;
|
||||||
|
|
||||||
|
const minValue = parseFloat(input.dataset.rangeMinValue || input.min || '0');
|
||||||
|
const maxValue = parseFloat(input.dataset.rangeMaxValue || input.max || '100');
|
||||||
|
const stepValue = parseFloat(input.step || '1') || 1;
|
||||||
|
|
||||||
|
function clampAndSnap(rawVal) {
|
||||||
|
let v = parseFloat(rawVal);
|
||||||
|
if (isNaN(v)) v = minValue;
|
||||||
|
if (v < minValue) v = minValue;
|
||||||
|
if (v > maxValue) v = maxValue;
|
||||||
|
return snapToStep(v, minValue, stepValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoth(fromManual) {
|
||||||
|
const current = fromManual && manualInput ? manualInput.value : input.value;
|
||||||
|
const value = clampAndSnap(current);
|
||||||
|
|
||||||
|
input.value = value;
|
||||||
|
|
||||||
|
if (output) {
|
||||||
|
output.textContent = formatRangeDisplay(value);
|
||||||
|
}
|
||||||
|
if (manualInput && !fromManual) {
|
||||||
|
manualInput.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRangeFill(input, minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', function () {
|
||||||
|
updateBoth(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (manualInput) {
|
||||||
|
manualInput.addEventListener('input', function () {
|
||||||
|
updateBoth(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial
|
||||||
|
updateBoth(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function formatRangeDisplay(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const numberValue = Number(value);
|
||||||
|
if (!isNaN(numberValue) && Number.isFinite(numberValue)) {
|
||||||
|
return Number.isInteger(numberValue) ? String(numberValue) : numberValue.toFixed(1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRangeFill(input, min, max) {
|
||||||
|
if (!input || !isFinite(min) || !isFinite(max) || max === min) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = parseFloat(input.value || min);
|
||||||
|
const ratio = Math.max(0, Math.min(1, (value - min) / (max - min))); // 0–1
|
||||||
|
const percent = ratio * 100;
|
||||||
|
|
||||||
|
// hijau dari 0 sampai nilai sekarang, sisanya putih
|
||||||
|
input.style.background =
|
||||||
|
'linear-gradient(90deg, #00c070 0% ' + percent + '%, #ffffff ' + percent + '% 100%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function snapToStep(value, min, step) {
|
||||||
|
if (!isFinite(step) || step <= 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const delta = value - min;
|
||||||
|
const steps = Math.round(delta / step);
|
||||||
|
const snapped = min + steps * step;
|
||||||
|
const decimals = getDecimalPlaces(step);
|
||||||
|
return parseFloat(snapped.toFixed(decimals));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDecimalPlaces(number) {
|
||||||
|
const value = number.toString();
|
||||||
|
if (value.indexOf('.') === -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value.length - value.indexOf('.') - 1;
|
||||||
|
}
|
||||||
|
|
||||||
function shouldDisableFieldForImmediate(field) {
|
function shouldDisableFieldForImmediate(field) {
|
||||||
if (!field || field.dataset.consentInput === '1') {
|
if (!field || field.dataset.consentInput === '1') {
|
||||||
return false;
|
return false;
|
||||||
@ -832,32 +1099,6 @@
|
|||||||
return value === '';
|
return value === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSelectSearch() {
|
|
||||||
if (!window.jQuery || !window.jQuery.fn || typeof window.jQuery.fn.select2 === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const $ = window.jQuery;
|
|
||||||
$('[data-select-search="true"]').each(function () {
|
|
||||||
const $select = $(this);
|
|
||||||
if ($select.data('select2')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$select.wrap('<div class="position-relative w-100"></div>');
|
|
||||||
$select.select2({
|
|
||||||
dropdownParent: $select.parent(),
|
|
||||||
width: '100%',
|
|
||||||
placeholder: $select.find('option[disabled]').first().text() || 'Pilih jawaban',
|
|
||||||
minimumResultsForSearch: 0
|
|
||||||
});
|
|
||||||
if ($select.is('[data-lainnya-select]')) {
|
|
||||||
const detailId = ($select.attr('name') || '').replace('jawaban[', '').replace(']', '');
|
|
||||||
$select.on('select2:select', function () {
|
|
||||||
handleLainnyaSelect(this, detailId, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupDualFormInputs() {
|
function setupDualFormInputs() {
|
||||||
document.querySelectorAll('[data-dual-wrapper]').forEach(function (wrapper) {
|
document.querySelectorAll('[data-dual-wrapper]').forEach(function (wrapper) {
|
||||||
const detailId = wrapper.getAttribute('data-dual-wrapper');
|
const detailId = wrapper.getAttribute('data-dual-wrapper');
|
||||||
@ -952,4 +1193,5 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
32
resources/views/soal/thank-you.blade.php
Normal file
32
resources/views/soal/thank-you.blade.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
@extends('layouts.template')
|
||||||
|
|
||||||
|
@section('title', 'Terima Kasih')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-6 col-md-8">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body text-center p-5">
|
||||||
|
<div class="rounded-circle bg-label-success d-inline-flex align-items-center justify-content-center mb-3" style="width: 72px; height: 72px;">
|
||||||
|
<svg viewBox="0 0 24 24" width="32" height="32" role="img" aria-hidden="true">
|
||||||
|
<path fill="currentColor" d="M12 2a10 10 0 1010 10A10.011 10.011 0 0012 2zm4.3 8.38l-5.07 5.08a1 1 0 01-1.42 0l-2.09-2.09a1 1 0 111.42-1.41l1.38 1.38 4.36-4.37a1 1 0 111.42 1.41z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="fw-semibold mb-2">Terima kasih sudah mengisi halaman ini</h3>
|
||||||
|
<p class="text-muted mb-4">
|
||||||
|
@if (session('success'))
|
||||||
|
{{ session('success') }}
|
||||||
|
@else
|
||||||
|
Jawaban Anda sudah kami terima dan akan dipergunakan untuk pengembangan layanan terbaik.
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
|
<a href="{{ route('soal.index') }}" class="btn btn-primary">
|
||||||
|
Kembali ke daftar kuesioner
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@ -8,8 +8,9 @@ use Illuminate\Support\Facades\Route;
|
|||||||
Route::get('/login', [AuthController::class, 'login']);
|
Route::get('/login', [AuthController::class, 'login']);
|
||||||
Route::get('/', [SoalController::class, 'index'])->name('soal.index');
|
Route::get('/', [SoalController::class, 'index'])->name('soal.index');
|
||||||
Route::post('/jawaban', [SoalController::class, 'store'])->name('soal.store');
|
Route::post('/jawaban', [SoalController::class, 'store'])->name('soal.store');
|
||||||
|
Route::get('/jawaban/terima-kasih', [SoalController::class, 'thankYou'])->name('soal.thankyou');
|
||||||
Route::get('/admin', [AdminController::class, 'index']);
|
Route::get('/admin', [AdminController::class, 'index']);
|
||||||
Route::post('/admin/get_data_pegawai_sudah_survey', [AdminController::class, 'get_data_pegawai_sudah_survey']);
|
Route::post('/admin/get_data_pegawai_sudah_survey', [AdminController::class, 'get_data_pegawai_sudah_survey']);
|
||||||
Route::get('/admin/dashboard_jawaban', [AdminController::class, 'dashboard_analisis']);
|
Route::get('/admin/dashboard_jawaban', [AdminController::class, 'dashboard_analisis']);
|
||||||
Route::post('/admin/get_data_dashboard_jawaban', [AdminController::class, 'get_data_dashboard_analisis']);
|
Route::post('/admin/get_data_dashboard_jawaban', [AdminController::class, 'get_data_dashboard_analisis']);
|
||||||
Route::get('/redirect-smart', [SoalController::class, 'redirectSmart']);
|
Route::get('/redirect-smart', [SoalController::class, 'redirectSmart']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user