Files
eCert-MBIP/resources/views/admin/questionnaires/show.blade.php
Saufi 2f76f94283 feat: questionnaire management (Fasa 6)
- QuestionnaireSetController: full CRUD + publish/archive
- QuestionController: store, update, destroy, reorder
- ProgramQuestionnaireController: attach, confirm, detach
- Public/QuestionnaireController: show form, submit responses, double-submit guard
- Views: admin questionnaire CRUD, program questionnaire assign, public form + thankyou/already

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 20:53:43 +08:00

295 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.admin')
@section('title', $set->title)
@section('header', $set->title)
@section('breadcrumb')
<li class="breadcrumb-item"><a href="{{ route('admin.questionnaires.index') }}">Soalselidik</a></li>
<li class="breadcrumb-item active">{{ Str::limit($set->title, 35) }}</li>
@endsection
@section('header-actions')
<div class="d-flex gap-2">
<a href="{{ route('admin.questionnaires.edit', $set) }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil me-1"></i> Edit
</a>
@if($set->status === 'draft')
<form method="POST" action="{{ route('admin.questionnaires.publish', $set) }}">
@csrf
<button class="btn btn-sm btn-success" onclick="return confirm('Terbitkan set soalselidik ini?')">
<i class="bi bi-send me-1"></i> Terbitkan
</button>
</form>
@elseif($set->status === 'published')
<form method="POST" action="{{ route('admin.questionnaires.archive', $set) }}">
@csrf
<button class="btn btn-sm btn-outline-secondary" onclick="return confirm('Arkibkan set soalselidik ini?')">
<i class="bi bi-archive me-1"></i> Arkib
</button>
</form>
@endif
</div>
@endsection
@section('content')
<div class="row g-4">
{{-- Left: Questions --}}
<div class="col-md-8">
{{-- Status Banner --}}
@if($set->status === 'draft')
<div class="alert alert-warning mb-3 small">
<i class="bi bi-exclamation-triangle me-2"></i>
Set ini masih dalam status <strong>Draf</strong>. Terbitkan setelah siap untuk dilampirkan ke program.
</div>
@elseif($set->status === 'archived')
<div class="alert alert-secondary mb-3 small">
<i class="bi bi-archive me-2"></i>
Set ini telah <strong>diarkibkan</strong> dan tidak boleh dilampirkan ke program baru.
</div>
@endif
{{-- Question List --}}
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
<h6 class="mb-0 fw-semibold">
<i class="bi bi-list-ul me-2 text-primary"></i>Senarai Soalan
<span class="badge bg-secondary ms-2">{{ $set->questions->count() }}</span>
</h6>
</div>
@if($set->questions->isEmpty())
<div class="card-body text-center py-5 text-muted">
<i class="bi bi-question-circle d-block fs-1 mb-3 opacity-25"></i>
Belum ada soalan. Tambah soalan menggunakan borang di sebelah kanan.
</div>
@else
<ul class="list-group list-group-flush" id="questionList">
@foreach($set->questions as $q)
<li class="list-group-item py-3" data-id="{{ $q->id }}">
<div class="d-flex justify-content-between align-items-start gap-2">
<div class="flex-grow-1">
<div class="d-flex align-items-center gap-2 mb-1">
<span class="badge bg-light text-dark border small">{{ $loop->iteration }}</span>
<span class="badge bg-primary bg-opacity-10 text-primary small">
{{ match($q->question_type) {
'rating' => 'Rating',
'single_choice' => 'Pilihan Tunggal',
'multiple_choice' => 'Pilihan Berganda',
'short_text' => 'Teks Pendek',
'long_text' => 'Teks Panjang',
default => $q->question_type,
} }}
</span>
@if($q->is_required)
<span class="badge bg-danger bg-opacity-10 text-danger small">Wajib</span>
@endif
</div>
<div class="fw-medium">{{ $q->question_text }}</div>
@if($q->options_json)
<div class="mt-1">
@foreach($q->options_json as $opt)
<span class="badge bg-light text-dark border me-1 small">{{ $opt }}</span>
@endforeach
</div>
@endif
</div>
<div class="d-flex gap-1 flex-shrink-0">
<button class="btn btn-sm btn-outline-secondary"
onclick="editQuestion({{ $q->id }}, @json($q->question_text), '{{ $q->question_type }}', {{ $q->is_required ? 'true' : 'false' }}, @json($q->options_json ?? []))">
<i class="bi bi-pencil"></i>
</button>
<form method="POST" action="{{ route('admin.questions.destroy', $q) }}"
onsubmit="return confirm('Padam soalan ini?')">
@csrf @method('DELETE')
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</div>
</li>
@endforeach
</ul>
@endif
</div>
{{-- Used In Programs --}}
@if($usedInPrograms->count())
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3">
<h6 class="mb-0 fw-semibold"><i class="bi bi-diagram-3 me-2 text-secondary"></i>Digunakan Dalam Program</h6>
</div>
<ul class="list-group list-group-flush">
@foreach($usedInPrograms as $program)
<li class="list-group-item d-flex justify-content-between align-items-center py-2">
<a href="{{ route('admin.programs.show', $program) }}" class="text-decoration-none small">
{{ $program->title }}
</a>
@if($program->pivot->is_confirmed ?? false)
<span class="badge bg-success">Disahkan</span>
@else
<span class="badge bg-warning text-dark">Belum Disahkan</span>
@endif
</li>
@endforeach
</ul>
</div>
@endif
</div>
{{-- Right: Add / Edit Question Form --}}
<div class="col-md-4">
<div class="card border-0 shadow-sm sticky-top" style="top:80px;">
<div class="card-header bg-white py-3">
<h6 class="mb-0 fw-semibold" id="formTitle"><i class="bi bi-plus-circle me-2 text-primary"></i>Tambah Soalan</h6>
</div>
<div class="card-body">
{{-- Add Question Form --}}
<form method="POST" id="questionForm" action="{{ route('admin.questions.store', $set) }}">
@csrf
<input type="hidden" name="_method" id="formMethod" value="POST">
<input type="hidden" name="_question_id" id="questionId" value="">
<div class="mb-3">
<label class="form-label small fw-medium">Soalan <span class="text-danger">*</span></label>
<textarea name="question_text" id="questionText" rows="3"
class="form-control form-control-sm @error('question_text') is-invalid @enderror"
placeholder="Taip soalan di sini..."></textarea>
@error('question_text')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="mb-3">
<label class="form-label small fw-medium">Jenis Soalan <span class="text-danger">*</span></label>
<select name="question_type" id="questionType" class="form-select form-select-sm" onchange="toggleOptions()">
<option value="rating">Rating (15)</option>
<option value="single_choice">Pilihan Tunggal</option>
<option value="multiple_choice">Pilihan Berganda</option>
<option value="short_text">Teks Pendek</option>
<option value="long_text">Teks Panjang</option>
</select>
</div>
<div class="mb-3 form-check">
<input type="checkbox" name="is_required" id="isRequired" class="form-check-input" value="1" checked>
<label class="form-check-label small" for="isRequired">Wajib dijawab</label>
</div>
<div id="optionsSection" class="mb-3 d-none">
<label class="form-label small fw-medium">Pilihan Jawapan</label>
<div id="optionsList">
<div class="input-group input-group-sm mb-2">
<input type="text" name="options[]" class="form-control" placeholder="Pilihan 1">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>
</div>
<div class="input-group input-group-sm mb-2">
<input type="text" name="options[]" class="form-control" placeholder="Pilihan 2">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary w-100" onclick="addOption()">
<i class="bi bi-plus me-1"></i> Tambah Pilihan
</button>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm w-100">
<i class="bi bi-check-lg me-1"></i> <span id="submitLabel">Tambah</span>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="cancelEdit" style="display:none;" onclick="resetForm()">
Batal
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
const setId = {{ $set->id }};
const storeUrl = "{{ route('admin.questions.store', $set) }}";
const updateBase = "{{ url('admin/questions') }}/";
function toggleOptions() {
const type = document.getElementById('questionType').value;
const section = document.getElementById('optionsSection');
section.classList.toggle('d-none', !['single_choice','multiple_choice'].includes(type));
}
function addOption() {
const list = document.getElementById('optionsList');
const count = list.querySelectorAll('input').length + 1;
const div = document.createElement('div');
div.className = 'input-group input-group-sm mb-2';
div.innerHTML = `<input type="text" name="options[]" class="form-control" placeholder="Pilihan ${count}">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>`;
list.appendChild(div);
}
function removeOption(btn) {
const list = document.getElementById('optionsList');
if (list.querySelectorAll('.input-group').length > 1) {
btn.closest('.input-group').remove();
}
}
function editQuestion(id, text, type, required, options) {
document.getElementById('formTitle').innerHTML = '<i class="bi bi-pencil me-2 text-warning"></i>Edit Soalan';
document.getElementById('submitLabel').textContent = 'Kemaskini';
document.getElementById('cancelEdit').style.display = '';
document.getElementById('questionId').value = id;
document.getElementById('questionText').value = text;
document.getElementById('questionType').value = type;
document.getElementById('isRequired').checked = required;
const form = document.getElementById('questionForm');
form.action = updateBase + id;
document.getElementById('formMethod').value = 'PUT';
toggleOptions();
const list = document.getElementById('optionsList');
list.innerHTML = '';
if (options && options.length) {
options.forEach((opt, i) => {
const div = document.createElement('div');
div.className = 'input-group input-group-sm mb-2';
div.innerHTML = `<input type="text" name="options[]" class="form-control" value="${opt}" placeholder="Pilihan ${i+1}">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>`;
list.appendChild(div);
});
}
form.scrollIntoView({ behavior: 'smooth' });
}
function resetForm() {
document.getElementById('formTitle').innerHTML = '<i class="bi bi-plus-circle me-2 text-primary"></i>Tambah Soalan';
document.getElementById('submitLabel').textContent = 'Tambah';
document.getElementById('cancelEdit').style.display = 'none';
document.getElementById('questionForm').reset();
document.getElementById('questionForm').action = storeUrl;
document.getElementById('formMethod').value = 'POST';
document.getElementById('questionId').value = '';
document.getElementById('optionsSection').classList.add('d-none');
const list = document.getElementById('optionsList');
list.innerHTML = `<div class="input-group input-group-sm mb-2">
<input type="text" name="options[]" class="form-control" placeholder="Pilihan 1">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>
</div>
<div class="input-group input-group-sm mb-2">
<input type="text" name="options[]" class="form-control" placeholder="Pilihan 2">
<button type="button" class="btn btn-outline-danger" onclick="removeOption(this)"><i class="bi bi-x"></i></button>
</div>`;
}
</script>
@endpush